Compare commits

...

37 Commits

Author SHA1 Message Date
Kegan Dougal 9e19d910c1 Bump to 0.2.0 and implement nuke db logic 2020-07-03 11:21:04 +01:00
Kegan Dougal 92fe2b8935 Only works on Go 1.14 now 2020-07-03 10:45:26 +01:00
Kegan Dougal 7e15090364 Wait for a room if it isn't there 2020-06-02 15:43:03 +01:00
Kegan Dougal d83c475bb7 v0.1.1 2020-06-02 15:25:50 +01:00
Kegan Dougal b4c7d54fc0 Add aliases automatically for pubilc rooms 2020-06-02 11:08:30 +01:00
Kegan Dougal e10d5eb0be Pin to the commit so it picks it up 2020-05-29 18:59:26 +01:00
Kegan Dougal ff6c4ac837 Use my fork so we can get IDBFS support 2020-05-29 18:26:47 +01:00
Kegan Dougal bcba891df1 reskindex on react too 2020-05-29 17:48:15 +01:00
Kegan Dougal 708a7146d5 Maybe build p2p without relying on yarn start 2020-05-29 17:23:09 +01:00
Kegan Dougal e4c67f39a6 Publish rooms where join_rule==public
- This currently doesn't add a room alias for the room, which should be fine
  provided the published room list returns data that includes servers in it.
2020-05-28 18:02:07 +01:00
Kegan Dougal a5ba31e127 Automatically prompt to set display name then view the room directory on first login 2020-05-28 13:19:11 +01:00
Kegan Dougal f6aceba566 Bump to 0.1.0 2020-05-22 14:28:48 +01:00
Kegan Dougal 35bcc152ea Write to indexeddb 2020-05-22 12:59:34 +01:00
Kegan Dougal ec45bb7976 Write dendrite version to the database 2020-05-19 15:33:18 +01:00
Kegan Dougal b0fb043c2b Add localforage support for persisting dendrite data 2020-05-19 11:54:08 +01:00
Kegan Dougal 4828869515 Restart dendrite when it exits 2020-05-18 18:26:30 +01:00
Kegan Dougal 671315c36e Use p2p branch of react-sdk 2020-04-17 11:28:59 +01:00
Kegan Dougal 9994c9ea65 Bugfixes for firefox 2020-04-02 18:20:34 +01:00
Kegan Dougal 9328519c29 Fix auto-register and constantly logging you out mess 2020-04-02 18:06:27 +01:00
Kegan Dougal 330aa285e6 Get login working on logout 2020-04-01 16:18:50 +01:00
Kegan Dougal 5c3f11a7c7 WIP: auto-registration 2020-03-31 18:05:34 +01:00
Kegan Dougal cdd32590fe Make dendrite load again 2020-03-26 12:33:31 +00:00
Kegan Dougal 52641c5674 [Full Changelog](https://github.com/vector-im/riot-web/compare/v1.5.13-rc.1...v1.5.13)
* Upgrade to JS SDK 5.1.1 and React SDK 2.2.3
 -----BEGIN PGP SIGNATURE-----
 
 iQHFBAABCAAvFiEEXqfg9wRho7y+vk1e9hUYBgMgJvkFAl5w39MRHHJlbGVhc2Vz
 QHJpb3QuaW0ACgkQ9hUYBgMgJvlVwQv/QXhOEaioBDRvIgBAd4Q0r1TNNjck4bNu
 kSnxusvuK8WBELPurF8IKk2Nx1rzO7AuY4nnjCJH/9Hzi+xKYAIv79j3kleJxlDB
 DZ2jTV3wLleAJQbmBtvphSaf5bYGH/A6KNcFBXzKID+fbHoY2reayw1FIe3pCeMe
 HDMjyBbkQj0jkHwiO+aMyHd/CBcOXw5MugOk/hi6ftzyQYWCxA+EhCERAC3P7XtT
 3/l5DlBepXqFgvnXnKRSyW91+Q6YXrHSmLLv/ZD7Iu5XMB2QvOiX8/yBEHCO8y0L
 SxVm/8fBnbyfafGb3+pL5LJk8cY0lde4X4Nwwi+ZgLO4v24B6d9KdOomg8a857Zp
 JgZtBmgapDpdoLmQXmnipWBB6QGoF+AqeO47E8weRB3mNko3BuLlfTNjFM4lkthJ
 h/xnVuCK7ruYdIoEHqTuOqkDEYOm2fJq0ShD+B77xfdf1B1GaM/0njLXrPCxgdHh
 avDv8YswPT17JMG+6SrAFI2Hb87DJQKg
 =x4LI
 -----END PGP SIGNATURE-----

Merge tag 'v1.5.13' into matthew/p2p

[Full Changelog](https://github.com/vector-im/riot-web/compare/v1.5.13-rc.1...v1.5.13)
* Upgrade to JS SDK 5.1.1 and React SDK 2.2.3
2020-03-26 11:50:30 +00:00
Kegan Dougal 6f43a14c43 Remove go-sqlite-js and just use sql.js directly 2020-03-26 10:35:40 +00:00
Kegan Dougal d80285d971 Spin up a new dendrite every time we get restarted 2020-03-16 12:57:15 +00:00
Kegan Dougal 3d74d336bf Use JSServer globals instead of fetchListener 2020-03-13 10:18:08 +00:00
Kegan Dougal 1f1f0f1264 Refresh service workers when new versions of sw.js come in
- Check once a minute
- When a new sw is here, skip waiting around for the old ones to die and claim the clients

NB: On Chrome, this results in a 2 minute wait with lost connectivity before logout. Refresh to
immediately use the new sw.

NBB: On Firefox, this automatically logs you out without needing to refresh.
2020-03-10 16:26:38 +00:00
Kegan Dougal bf13ec2285 Claim clients to avoid the refresh 2020-03-10 14:46:18 +00:00
Kegan Dougal 4d1f969c4d Latest updates; set process stuff to make prometheus happy 2020-03-10 14:28:12 +00:00
Kegan Dougal 0935ccd737 Latest updates 2020-03-04 11:15:06 +00:00
Kegan Dougal d2635bd282 Stuff from matrix meetup 2020-02-27 10:10:18 +00:00
Kegan Dougal 462fa91938 Better logging 2020-02-25 18:05:45 +00:00
Kegan Dougal 836236ea9b Rely on symlinking sqlite/libp2p 2020-02-25 16:46:41 +00:00
Kegan Dougal 3fcc3c8bfd Allow service workers to have the scope / which is needed for the interceptor 2020-02-25 15:27:31 +00:00
Matthew Hodgson 349d3e3d47 make it work 2020-01-23 20:16:08 -07:00
Matthew Hodgson 8a41f956f6 merge develop 2020-01-23 19:15:06 -07:00
Matthew Hodgson 80411d38f7 first cut at embedding dendrite into riot as a service-worker 2020-01-23 13:22:52 -07:00
10 changed files with 1443 additions and 77 deletions

View File

@ -50,6 +50,8 @@
"install:electron": "electron-builder install-app-deps",
"dist": "scripts/package.sh",
"start": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n reskindex,reskindex-react,res,riot-js \"yarn reskindex:watch\" \"yarn reskindex:watch-react\" \"yarn start:res\" \"yarn start:js\"",
"build:p2p": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n reskindex,reskindex-react,res,riot-js \"yarn reskindex\" \"node scripts/yarn-sub.js matrix-react-sdk reskindex\" \"node scripts/copy-res.js\" \"yarn build:p2p-bundle\"",
"build:p2p-bundle": "cross-env webpack -p --progress --bail --mode development",
"start:res": "node scripts/copy-res.js -w",
"start:js": "webpack-dev-server --host=0.0.0.0 --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --progress --mode development",
"electron": "yarn build && yarn install:electron && electron .",
@ -66,14 +68,16 @@
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
"gfm.css": "^1.1.2",
"highlight.js": "^9.13.1",
"localforage": "^1.7.3",
"matrix-js-sdk": "5.1.1",
"matrix-react-sdk": "2.2.3",
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#p2p",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"postcss-easings": "^2.0.0",
"prop-types": "^15.7.2",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"sanitize-html": "^1.19.1",
"sql.js": "github:Kegsay/sql.js#d55d2069673d0a62ffe729a8a6c96b1328ae396c",
"ua-parser-js": "^0.7.19",
"url": "^0.11.0"
},

View File

@ -21,19 +21,23 @@ limitations under the License.
import olmWasmPath from 'olm/olm.wasm';
import React from 'react';
import PropTypes from 'prop-types';
// add React and ReactPerf to the global namespace, to make them easier to
// access via the console
global.React = React;
import ReactDOM from 'react-dom';
import Matrix from 'matrix-js-sdk';
import * as sdk from 'matrix-react-sdk';
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
import Modal from 'matrix-react-sdk/src/Modal';
import * as VectorConferenceHandler from 'matrix-react-sdk/src/VectorConferenceHandler';
import * as languageHandler from 'matrix-react-sdk/src/languageHandler';
import {_t, _td, newTranslatableError} from 'matrix-react-sdk/src/languageHandler';
import AutoDiscoveryUtils from 'matrix-react-sdk/src/utils/AutoDiscoveryUtils';
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
import dis from "matrix-react-sdk/src/dispatcher";
import url from 'url';
@ -43,6 +47,7 @@ import ElectronPlatform from './platform/ElectronPlatform';
import WebPlatform from './platform/WebPlatform';
import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg';
import {getCachedRoomIDForAlias} from 'matrix-react-sdk/src/RoomAliasCache';
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import {setTheme} from "matrix-react-sdk/src/theme";
@ -123,6 +128,184 @@ function onNewScreen(screen) {
const hash = '#/' + screen;
lastLocationHashSet = hash;
window.location.hash = hash;
if (!window.matrixChat) {
return;
}
let creds = null;
if (screen === "register" || screen === "login" || screen === "welcome") {
autoRegister().then((newCreds) => {
creds = newCreds;
return window.matrixChat.onUserCompletedLoginFlow(newCreds, "-");
}, (err) => {
console.error("Failed to auto-register:", err)
}).then(() => {
// first time user
if (creds._registered) {
p2pFirstTimeSetup();
}
})
} else if (screen.startsWith("room/")) {
// room/!foo:bar
// room/#foo:bar
// if this room is public then make sure it is published.
p2pEnsurePublished(screen.split("/")[1])
}
}
class P2PDisplayNameDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <BaseDialog
onFinished={this.props.onFinished}
title={ _t('Set a display name:')}
>
<ChangeDisplayName onFinished={this.props.onFinished} />
<DialogButtons
primaryButton={_t('OK')}
onPrimaryButtonClick={this.props.onFinished}
hasCancel={false}
/>
</BaseDialog>;
}
}
function p2pFirstTimeSetup() {
// Prompt them to set a display name
Modal.createDialog(P2PDisplayNameDialog,
{
onFinished: () => {
// View the room directory after display name has been sorted out
dis.dispatch({
action: 'view_room_directory',
});
},
}, null, /* priority = */ false, /* static = */ true,
);
}
async function fetchRoom(roomId) {
const client = MatrixClientPeg.get();
let room = client.getRoom(roomId);
if (room) {
return room;
}
console.log("p2pEnsurePublished fetchRoom waiting for room... ", roomId);
room = await new Promise((resolve, reject) => {
let fulfilled = false;
let cb = function(room){
if (fulfilled) {
return;
}
let newRoomId = room.roomId;
if (roomId === newRoomId) {
fulfilled = true;
console.log("p2pEnsurePublished fetchRoom found ", roomId);
resolve(room);
}
}
client.on("Room", cb);
setTimeout(() => {
if (fulfilled) {
return;
}
console.log("p2pEnsurePublished fetchRoom timed out ", roomId);
fulfilled = true;
client.removeListener("Room", cb);
reject(new Error("timed out waiting to see room " + roomId));
}, 60 * 1000); // wait 60s
});
return room;
}
async function p2pEnsurePublished(roomIdOrAlias) {
// If the room has just been created, we need to wait for the join_rules to come down /sync
// If the app has just been refreshed, we need to wait for the DB to be loaded.
// Since we don't really care when this is done, just sleep a bit.
await sleep(3000);
console.log("p2pEnsurePublished ", roomIdOrAlias);
try {
const client = MatrixClientPeg.get();
// convert alias to room ID
let roomId;
let aliasLocalpart;
if (roomIdOrAlias.startsWith("!")) {
roomId = roomIdOrAlias;
} else {
roomId = getCachedRoomIDForAlias(roomIdOrAlias);
// extract the localpart so we can republish this alias on our server
aliasLocalpart = roomIdOrAlias.split(":")[0].substring(1)
}
// fetch the join_rules, check if public
const room = await fetchRoom(roomId);
if (!room) {
throw new Error("No room for room ID: " + roomId);
}
if (!aliasLocalpart) {
const roomName = room.currentState.getStateEvents("m.room.name", "");
if (roomName) {
aliasLocalpart = roomName.getContent().name;
// room alias grammar is poorly defined. Synapse rejects whitespace, Riot barfs on slashes, it's a mess.
// so for now, let's just do A-Za-z0-9_-
aliasLocalpart = aliasLocalpart.replace(/[^A-Za-z0-9_-]/g, "")
} else {
// use the random part of the room ID as a fallback.
aliasLocalpart = roomId.split(":")[0].substring(1)
}
}
const joinRules = room.currentState.getStateEvents("m.room.join_rules", "");
if (!joinRules) {
throw new Error("No join_rules for room ID: " + roomId);
}
const isPublic = joinRules.getContent().join_rule === "public";
if (isPublic) {
// make sure that there is an alias mapping
try {
for(let i = 0; i < 2; i++) {
const newRoomAlias = `#${aliasLocalpart}:${client.getDomain()}`;
let exists = false;
let matches = false;
try {
const aliasResponse = await client.getRoomIdForAlias(newRoomAlias);
matches = aliasResponse.room_id === roomId;
exists = true;
} catch (err) {}
console.log("p2pEnsurePublished: room ID:", roomId, " want alias: ", newRoomAlias, " exists=", exists, " matches=", matches);
if (!exists) {
await client.createAlias(newRoomAlias, roomId);
break;
} else if (!matches) {
// clashing room alias, use the room ID.
aliasLocalpart = roomId.split(":")[0].substring(1);
} else {
// exists and matches, do nothing
break;
}
}
} catch (err) {
console.log("p2pEnsurePublished: problem creating alias: ", err);
}
// publish the room
await client.setRoomDirectoryVisibility(roomId, "public");
console.log("p2pEnsurePublished: Now published.");
} else {
// unpublish the room
await client.setRoomDirectoryVisibility(roomId, "private");
console.log("p2pEnsurePublished: Now hidden.");
}
} catch (err) {
console.log("p2pEnsurePublished encountered an error: ", err);
}
}
// We use this to work out what URL the SDK should
@ -172,6 +355,53 @@ function onTokenLoginCompleted() {
window.location.href = formatted;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function autoRegister() {
console.log("dendrite: Auto-registration in progress");
const cli = Matrix.createClient({
baseUrl: "https://p2p.riot.im",
});
const password = "this should be really really secure";
// make sure the server is up (active service worker)
await navigator.serviceWorker.ready;
// on Firefix, the ready promise resolves just prior to activation
// on Chrome, the ready promise resolves just after activation.
// We need to make requests AFTER we have been activated, else the /register request
// will fail.
await sleep(10);
let response = null;
let didRegister = false;
try {
response = await cli.register("p2p", password, "", { type: "m.login.dummy" });
console.log("dendrite: Auto-registration done ", response);
didRegister = true;
} catch (err) {
console.error("dendrite: failed to register, trying to login:", err)
response = await cli.login("m.login.password", {
identifier: {
type: "m.id.user",
user: "p2p",
},
password: password,
initial_device_display_name: "p2p-dendrite",
})
}
return {
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: cli.getHomeserverUrl(),
accessToken: response.access_token,
guest: cli.isGuest(),
_registered: didRegister,
};
}
export async function loadApp() {
// XXX: the way we pass the path to the worker script from webpack via html in body's dataset is a hack
// but alternatives seem to require changing the interface to passing Workers to js-sdk
@ -183,7 +413,8 @@ export async function loadApp() {
// make sure the indexeddb script is present, so fail hard.
throw new Error("Missing indexeddb worker script!");
}
MatrixClientPeg.setIndexedDbWorkerScript(vectorIndexeddbWorkerScript);
// MatrixClientPeg.setIndexedDbWorkerScript(vectorIndexeddbWorkerScript);
CallHandler.setConferenceHandler(VectorConferenceHandler);
window.addEventListener('hashchange', onHashChange);
@ -274,6 +505,45 @@ export async function loadApp() {
return;
}
// load dendrite, if available
const vectorDendriteWorkerScript = document.body.dataset.vectorDendriteWorkerScript;
if (vectorDendriteWorkerScript && 'serviceWorker' in navigator) {
console.log("dendrite code exec... ", document.readyState);
const loadDendriteSw = ()=>{
console.log("Registering dendrite sw...", vectorDendriteWorkerScript);
console.log("swjs: invoke navigator.serviceWorker.register")
navigator.serviceWorker.register(vectorDendriteWorkerScript, { scope: "/" }).then(function(registration) {
console.log("swjs: navigator.serviceWorker.register resolved", registration)
// Registration was successful
console.log('ServiceWorker sw.js registration successful with scope: ', registration.scope);
// periodically check for updates
setInterval(function() {
console.log("swjs invoke registration.update")
registration.update();
}, 1000 * 60 * 30) // once every 30 minutes
}, (err)=>{
// registration failed :(
console.log('dendrite: ServiceWorker registration failed: ', err);
});
// First, do a one-off check if there's currently a
// service worker in control.
if (navigator.serviceWorker.controller) {
console.log('dendrite: This page is currently controlled by:', navigator.serviceWorker.controller);
}
// Then, register a handler to detect when a new or
// updated service worker takes control.
navigator.serviceWorker.oncontrollerchange = function() {
console.log('dendrite: This page is now controlled by:', navigator.serviceWorker.controller);
};
}
if (document.readyState === "loading") {
window.addEventListener('DOMContentLoaded', loadDendriteSw);
} else {
loadDendriteSw();
}
}
const validBrowser = checkBrowserFeatures();
const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');

283
src/vector/dendrite-sw.js Normal file
View File

@ -0,0 +1,283 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
// Copyright 2020 New Vector Ltd
//
// 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.
// Dendrite Service Worker version
// Bumping the patch version of this has no side-effects.
// Bumping the minor version of this will delete databases.
const version = "0.2.0";
const isVersionBump = function(oldVer, newVer) {
oldVer = oldVer || "";
const newSegments = newVer.split(".");
const oldSegments = oldVer.split(".");
if (oldSegments.length != 3) {
return true; // brand new
}
return newSegments[1] > oldSegments[1];
}
const bundle_path = self.location.href.replace("/dendrite_sw.js", "")
const id = Math.random();
console.log("swjs: ", id," dendrite-sw.js file running...")
self.registration.addEventListener('updatefound', () => {
console.log("swjs: ", id," updatefound registration event fired")
const newWorker = self.registration.installing;
if (!newWorker) {
console.log("swjs: ", id," updatefound registration event fired, no installing worker")
return;
}
newWorker.addEventListener('statechange', () => {
console.log("swjs: ", id," worker statechange: ", newWorker.state)
});
})
self.importScripts(`${bundle_path}/wasm_exec.js`,
`${bundle_path}/go_http_bridge.js`,
`${bundle_path}/sqlitejs.js`,
`${bundle_path}/localforage.js`);
let isWriting = false;
async function writeDatabasesToIndexedDB() {
while (true) {
await sleep(1000 * 30);
try {
await syncfs(false);
}
catch (err) {
console.error("syncfs: failed to write to IDB:", err);
}
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function syncfs(isStartup) {
return new Promise((resolve, reject) => {
global._go_sqlite.FS.syncfs(isStartup, function(err) {
if (err) {
console.error("syncfs failed:", err);
reject(err);
return;
}
console.log("syncfs OK");
resolve();
});
});
}
function deleteDatabase(name) {
return new Promise(function(resolve, reject) {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = function () {
console.log("Deleted database successfully: ", name);
resolve();
};
req.onerror = function (event) {
console.log("Couldn't delete database: ", name, event);
reject(new Error("failed to delete database"));
};
});
}
async function initDendrite() {
console.log(`dendrite-sw.js: v${version} SW init`)
// check if we need to purge databases (minor version bump)
const prevVer = await global.localforage.getItem("dendrite_version")
if (prevVer != version) {
const nukeDatabase = isVersionBump(prevVer, version);
console.log(`dendrite-sw.js: previous ver ${prevVer} current ${version} nuke databases: ${nukeDatabase}`);
if (nukeDatabase) {
await deleteDatabase("/idb");
}
}
global.process = {
pid: 1,
env: {
DEBUG: "*",
}
};
// we do this for prometheus so it doesn't panic on init()
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
const config = {
locateFile: filename => `${bundle_path}/../../sql-wasm.wasm`
}
const go = new Go();
await sqlitejs.init(config);
// periodically write databases to disk
if (!isWriting) {
isWriting = true;
// have to do this before we start dendrite for hopefully obvious reasons...
console.log("syncfs", global._go_sqlite.IDBFS);
global._go_sqlite.FS.mkdir("/idb");
//global._go_sqlite.FS.unmount("/");
console.log("syncfs at /idb mkdir ok");
global._go_sqlite.FS.mount(global._go_sqlite.IDBFS, {}, "/idb");
console.log("syncfs mounted");
await syncfs(true); // load from IDB
writeDatabasesToIndexedDB();
}
console.log(`dendrite-sw.js: v${version} starting dendrite.wasm...`)
const result = await WebAssembly.instantiateStreaming(fetch(`${bundle_path}/../../dendrite.wasm`), go.importObject)
go.run(result.instance).then(() => {
console.log(`dendrite-sw.js: v${version} dendrite.wasm terminated, restarting...`);
// purge databases and p2p nodes.
global._go_js_server = undefined;
global._go_libp2p_nodes.forEach((n) => {
n.stop();
});
global._go_libp2p_nodes = [];
global._go_sqlite_dbs.clear();
initDendritePromise = initDendrite();
});
// make fetch calls go through this sw - notably if a page registers a sw, it does NOT go through any sw by default
// unless you refresh or call this function.
console.log(`dendrite-sw.js: v${version} claiming open browser tabs`)
console.log("swjs: ", id," invoke self.clients.claim()")
self.clients.claim();
let serverIsUp = false;
for (let i = 0; i < 30; i++) { // 3s
if (global._go_js_server) {
console.log("swjs: ", id," init dendrite promise resolving");
serverIsUp = true;
break;
}
await sleep(100);
}
if (!serverIsUp) {
throw new Error("Timed out waiting for _go_js_server to be set.");
}
// persist the new version
await global.localforage.setItem("dendrite_version", version);
}
let initDendritePromise = initDendrite();
self.addEventListener('install', function(event) {
console.log("swjs: ", id," install event fired:", event)
console.log(`dendrite-sw.js: v${version} SW install`)
// Tell the browser to kill old sw's running in other tabs and replace them with this one
// This may cause spontaneous logouts.
console.log("swjs: ", id," invoke self.skipWaiting")
self.skipWaiting();
})
self.addEventListener('activate', function(event) {
console.log("swjs: ", id," activate event fired")
console.log(`dendrite-sw.js: v${version} SW activate`)
event.waitUntil(initDendritePromise)
})
async function sendRequestToGo(event) {
await initDendritePromise; // this sets the global fetch listener
if (!global._go_js_server || !global._go_js_server.fetch) {
console.log(`dendrite-sw.js: v${version} no fetch listener present for ${event.request.url}`);
return
}
console.log(`dendrite-sw.js: v${version} forwarding ${event.request.url} to Go`);
const req = event.request
let reqHeaders = ''
if (req.headers) {
for (const header of req.headers) {
// FIXME: is this a safe header encoding?
reqHeaders += `${header[0]}: ${header[1]}\n`
}
}
let jj = null;
if (req.method === "POST" || req.method === "PUT") {
jj = await req.json();
jj = JSON.stringify(jj);
reqHeaders += `Content-Length: ${new Blob([jj]).size}`; // include utf-8 chars properly
}
if (reqHeaders.length > 0) {
reqHeaders = `\r\n${reqHeaders}`
}
// Replace the timeout value for /sync calls to be 20s not 30s because Firefox
// will aggressively cull service workers after a 30s idle period. Chrome doesn't.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1378587
const fullurl = req.url.replace("timeout=30000", "timeout=20000");
const reqString = `${req.method} ${fullurl} HTTP/1.0${reqHeaders}\r\n\r\n${jj ? jj : ''}`
const res = await global._go_js_server.fetch(reqString)
if (res.error) {
console.error(`dendrite-sw.js: v${version} Error for request: ${event.request.url} => ${res.error}`)
return
}
const respString = res.result;
const m = respString.match(/^(HTTP\/1.[01]) ((.*?) (.*?))(\r\n([^]*?)?(\r\n\r\n([^]*?)))?$/)
if (!m) {
console.warn("couldn't parse resp", respString);
return;
}
const response = {
"proto": m[1],
"status": m[2],
"statusCode": parseInt(m[3]),
"headers": m[6],
"body": m[8],
}
const respHeaders = new Headers()
const headerLines = response.headers.split('\r\n')
for (const headerLine of headerLines) {
// FIXME: is this safe header parsing? Do we need to worry about line-wrapping?
const match = headerLine.match(/^(.+?): *(.*?)$/)
if (match) {
respHeaders.append(match[1], match[2])
}
else {
console.log("couldn't parse headerLine ", headerLine)
}
}
return new Response(response.body, {
status: response.statusCode,
headers: respHeaders,
})
}
self.addEventListener('fetch', function(event) {
event.respondWith((async () => {
/*
// If this is a page refresh for the current page, then shunt in the new sw
// https://github.com/w3c/ServiceWorker/issues/1238
if (event.request.mode === "navigate" && event.request.method === "GET" && registration.waiting && (await clients.matchAll()).length < 2) {
console.log("Forcing new sw.js into page")
registration.waiting.postMessage('skipWaiting');
return new Response("", {headers: {"Refresh": "0"}});
} */
if (event.request.url.match(/\/_matrix\/client/)) {
return await sendRequestToGo(event);
}
else {
return fetch(event.request);
}
})());
})

BIN
src/vector/dendrite.wasm Executable file

Binary file not shown.

View File

@ -49,7 +49,9 @@
<% }
} %>
</head>
<body style="height: 100%;" data-vector-indexeddb-worker-script="<%= htmlWebpackPlugin.files.chunks['indexeddb-worker'].entry %>">
<body style="height: 100%;"
data-vector-indexeddb-worker-script="<%= htmlWebpackPlugin.files.chunks['indexeddb_worker'].entry %>"
data-vector-dendrite-worker-script="<%= htmlWebpackPlugin.files.chunks['dendrite_sw'].entry %>">
<noscript>Sorry, Riot requires JavaScript to be enabled.</noscript> <!-- TODO: Translate this? -->
<section id="matrixchat" style="height: 100%; overflow: auto;"></section>
<script src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"></script>

View File

@ -31,7 +31,7 @@ import './modernizr';
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
// navigator.serviceWorker.register('sw.js');
}
// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference

22
src/vector/sqlitejs.js Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2020 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 initSqlJs from 'sql.js'
export function init(config) {
return initSqlJs(config).then(SQL => {
global._go_sqlite = SQL;
console.log("Loaded sqlite")
})
}

591
src/vector/wasm_exec.js Normal file
View File

@ -0,0 +1,591 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
// global.fs = require("fs");
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!global.process) {
global.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!global.crypto) {
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder) {
global.TextEncoder = require("util").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("util").TextDecoder;
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
global.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number") {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
if (v === 0) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 1, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
switch (v) {
case undefined:
this.mem.setFloat64(addr, 0, true);
return;
case null:
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 2, true);
return;
case true:
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 3, true);
return;
case false:
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 4, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 1;
switch (typeof v) {
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime1() (sec int64, nsec int32)
"runtime.walltime1": (sp) => {
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map(); // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length < 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
go._pendingEvent = { id: 0 };
go._resume();
}
});
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

View File

@ -32,8 +32,15 @@ module.exports = (env, argv) => {
entry: {
"bundle": "./src/vector/index.js",
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
"indexeddb_worker": "./src/vector/indexeddb-worker.js",
"dendrite_sw": "./src/vector/dendrite-sw.js",
"mobileguide": "./src/vector/mobile_guide/index.js",
"sqlitejs": "./src/vector/sqlitejs.js",
"localforage": "./node_modules/localforage/dist/localforage.min.js",
"go_http_bridge": "./node_modules/go-http-js-libp2p/js/bridge.js",
"sql_wasm": "./node_modules/sql.js/dist/sql-wasm.wasm",
"dendrite_wasm": "./src/vector/dendrite.wasm",
"wasm_exec": "./src/vector/wasm_exec.js",
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
// CSS themes
@ -228,7 +235,9 @@ module.exports = (env, argv) => {
loader: "file-loader",
type: "javascript/auto", // https://github.com/webpack/webpack/issues/6725
options: {
name: '[name].[hash:7].[ext]',
// fixme: reintroduce [hash] once we can figure out how to pass it into the
// dendrite service worker nicely
name: '[name].[ext]',
outputPath: '.',
},
},
@ -282,6 +291,10 @@ module.exports = (env, argv) => {
]
},
node: {
fs: 'empty' // needed for sql.js
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
@ -339,6 +352,8 @@ module.exports = (env, argv) => {
// chunks even after the app is redeployed.
filename: "bundles/[hash]/[name].js",
chunkFilename: "bundles/[hash]/[name].js",
libraryTarget: "var",
library: "[name]",
},
// configuration for the webpack-dev-server
@ -346,15 +361,20 @@ module.exports = (env, argv) => {
// serve unwebpacked assets from webapp.
contentBase: './webapp',
// Only output errors, warnings, or new compilations.
// This hides the massive list of modules.
stats: 'minimal',
stats: {
// don't fill the console up with a mahoosive list of modules
chunks: false,
},
headers: {
"Service-Worker-Allowed": "/"
},
// hot module replacement doesn't work (I think we'd need react-hot-reload?)
// so webpack-dev-server reloads the page on every update which is quite
// tedious in Riot since that can take a while.
hot: false,
inline: false,
writeToDisk: true,
},
};
};

310
yarn.lock
View File

@ -33,13 +33,20 @@
optionalDependencies:
chokidar "^2.1.8"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
dependencies:
"@babel/highlight" "^7.8.3"
"@babel/code-frame@^7.5.5":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
dependencies:
"@babel/highlight" "^7.10.1"
"@babel/compat-data@^7.8.4":
version "7.8.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.5.tgz#d28ce872778c23551cbb9432fc68d28495b613b9"
@ -264,6 +271,11 @@
dependencies:
"@babel/types" "^7.8.3"
"@babel/helper-validator-identifier@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
"@babel/helper-wrap-function@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610"
@ -283,13 +295,13 @@
"@babel/traverse" "^7.8.4"
"@babel/types" "^7.8.3"
"@babel/highlight@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
"@babel/highlight@^7.10.1", "@babel/highlight@^7.8.3":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
dependencies:
"@babel/helper-validator-identifier" "^7.10.1"
chalk "^2.0.0"
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4":
@ -907,7 +919,14 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.8.3":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
@ -2138,9 +2157,9 @@ bluebird@^3.5.0, bluebird@^3.5.5:
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
blueimp-canvas-to-blob@^3.5.0:
version "3.17.0"
resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.17.0.tgz#d796e72aa1fd37f9471e0396e64d323956564a23"
integrity sha512-LcuVZZtKtE4yUrLb0IQA7ndMm19gooDTrzvjjkvmQ1MhkFlSzTw+8Ftp+/ckfGeP2UDQvDsPyy8mIT69itAqbg==
version "3.27.0"
resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.27.0.tgz#a2bd5c43587b95dedf0f6998603452d1bfcc9b9e"
integrity sha512-AcIj+hCw6WquxzJuzC6KzgYmqxLFeTWe88KuY2BEIsW1zbEOfoinDAGlhyvFNGt+U3JElkVSK7anA1FaSdmmfA==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
@ -2390,7 +2409,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.1.0, buffer@^5.4.3:
buffer@^5.1.0:
version "5.4.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
@ -2398,6 +2417,14 @@ buffer@^5.1.0, buffer@^5.4.3:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@^5.4.3:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
builder-util-runtime@8.6.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.6.0.tgz#b7007c30126da9a90e99932128d2922c8c178649"
@ -2468,7 +2495,28 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
cacache@^12.0.0, cacache@^12.0.2:
cacache@^12.0.0:
version "12.0.4"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
dependencies:
bluebird "^3.5.5"
chownr "^1.1.1"
figgy-pudding "^3.5.1"
glob "^7.1.4"
graceful-fs "^4.1.15"
infer-owner "^1.0.3"
lru-cache "^5.1.1"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
promise-inflight "^1.0.1"
rimraf "^2.6.3"
ssri "^6.0.1"
unique-filename "^1.1.1"
y18n "^4.0.0"
cacache@^12.0.2:
version "12.0.3"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
@ -3744,9 +3792,9 @@ diff-dom@^4.1.3:
updates "^8.5.2"
diff-match-patch@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
version "1.0.5"
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
diff-sequences@^24.9.0:
version "24.9.0"
@ -3904,9 +3952,9 @@ domutils@^1.5.1, domutils@^1.7.0:
domelementtype "1"
domutils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.0.0.tgz#15b8278e37bfa8468d157478c58c367718133c08"
integrity sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==
version "2.1.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.1.0.tgz#7ade3201af43703fde154952e3a868eb4b635f16"
integrity sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==
dependencies:
dom-serializer "^0.2.1"
domelementtype "^2.0.1"
@ -4134,7 +4182,12 @@ entities@^1.1.1, "entities@~ 1.1.1":
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0, entities@~2.0.0:
entities@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436"
integrity sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==
entities@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
@ -4717,9 +4770,9 @@ fbjs@^0.8.4, fbjs@^0.8.9:
ua-parser-js "^0.7.18"
figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^2.0.0:
version "2.0.0"
@ -4900,15 +4953,15 @@ flux@2.1.1:
fbjs "0.1.0-alpha.7"
immutable "^3.7.4"
focus-lock@^0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7"
integrity sha512-Dx69IXGCq1qsUExWuG+5wkiMqVM/zGx/reXSJSLogECwp3x6KeNQZ+NAetgxEFpnC41rD8U3+jRCW68+LNzdtw==
focus-lock@^0.6.7:
version "0.6.8"
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.8.tgz#61985fadfa92f02f2ee1d90bc738efaf7f3c9f46"
integrity sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og==
focus-visible@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.0.2.tgz#4fae9cf40458b73c10701c9774c462e3ccd53caf"
integrity sha512-zT2fj/bmOgEBjqGbURGlowTmCwsIs3bRDMr/sFZz8Ly7VkEiwuCn9swNTL3pPuf8Oua2de7CLuKdnuNajWdDsQ==
version "5.1.0"
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.1.0.tgz#4b9d40143b865f53eafbd93ca66672b3bf9e7b6a"
integrity sha512-nPer0rjtzdZ7csVIu233P2cUm/ks/4aVSI+5KUkYrYpgA7ujgC3p6J7FtFU+AIMWwnwYQOB/yeiOITxFeYIXiw==
follow-redirects@^1.0.0:
version "1.10.0"
@ -5303,11 +5356,16 @@ got@^9.6.0:
to-readable-stream "^1.0.0"
url-parse-lax "^3.0.0"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graceful-fs@^4.1.15, graceful-fs@^4.1.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -5517,9 +5575,9 @@ html-encoding-sniffer@^1.0.2:
whatwg-encoding "^1.0.1"
html-entities@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
version "1.3.1"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44"
integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==
html-escaper@^2.0.0:
version "2.0.0"
@ -5740,6 +5798,11 @@ ignore@^5.1.4:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immutable@^3.7.4:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
@ -7159,6 +7222,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@ -7219,6 +7289,13 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4
emojis-list "^3.0.0"
json5 "^1.0.1"
localforage@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204"
integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==
dependencies:
lie "3.1.1"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@ -7520,10 +7597,9 @@ matrix-mock-request@^1.2.3:
bluebird "^3.5.0"
expect "^1.20.2"
matrix-react-sdk@2.2.3:
"matrix-react-sdk@github:matrix-org/matrix-react-sdk#p2p":
version "2.2.3"
resolved "https://registry.yarnpkg.com/matrix-react-sdk/-/matrix-react-sdk-2.2.3.tgz#2687af6acb0f994cbb17a1c4cc37703c437f6080"
integrity sha512-tQVa3uAT//f0+Zw+8sKpRl+/gEvelck5kH0V/BOtM+UrreG66L42KgcXNRDlmO8DjD4iEttcoSHsLGr+XZuXyQ==
resolved "https://codeload.github.com/matrix-org/matrix-react-sdk/tar.gz/a5c0c5e1b2f6e499bff5646989ea583c0e01636b"
dependencies:
"@babel/runtime" "^7.8.3"
blueimp-canvas-to-blob "^3.5.0"
@ -7815,11 +7891,16 @@ minimist@1.1.x:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, "minimist@~ 1.2.0":
minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
"minimist@~ 1.2.0":
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@ -8011,9 +8092,9 @@ no-case@^2.2.0:
lower-case "^1.1.1"
node-fetch-npm@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
integrity sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg==
dependencies:
encoding "^0.1.11"
json-parse-better-errors "^1.0.0"
@ -8464,7 +8545,14 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2:
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-limit@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
@ -9576,7 +9664,7 @@ postcss@^5.0.18, postcss@^5.0.4:
source-map "^0.5.6"
supports-color "^3.2.3"
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7:
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7:
version "7.0.27"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9"
integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==
@ -9585,6 +9673,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^7.0.27:
version "7.0.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.31.tgz#332af45cb73e26c0ee2614d7c7fb02dfcc2bd6dd"
integrity sha512-a937VDHE1ftkjk+8/7nj/mrjtmkn69xxzJgRETXdAUU+IgOYPQNJF17haGWbeDxSyk++HA14UA98FurvPyBJOA==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -9792,11 +9889,16 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.5.2, qs@^6.6.0:
qs@^6.5.2:
version "6.9.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
qs@^6.6.0:
version "6.9.4"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@ -9932,12 +10034,12 @@ react-dom@^16.9.0:
scheduler "^0.18.0"
react-focus-lock@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.2.1.tgz#1d12887416925dc53481914b7cedd39494a3b24a"
integrity sha512-47g0xYcCTZccdzKRGufepY8oZ3W1Qg+2hn6u9SHZ0zUB6uz/4K4xJe7yYFNZ1qT6m+2JDm82F6QgKeBTbjW4PQ==
version "2.3.1"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.3.1.tgz#9d5d85899773609c7eefa4fc54fff6a0f5f2fc47"
integrity sha512-j15cWLPzH0gOmRrUg01C09Peu8qbcdVqr6Bjyfxj80cNZmH+idk/bNBYEDSmkAtwkXI+xEYWSmHYqtaQhZ8iUQ==
dependencies:
"@babel/runtime" "^7.0.0"
focus-lock "^0.6.6"
focus-lock "^0.6.7"
prop-types "^15.6.2"
react-clientside-effect "^1.2.2"
use-callback-ref "^1.2.1"
@ -9949,7 +10051,12 @@ react-focus-lock@^2.2.1:
dependencies:
gemini-scrollbar matrix-org/gemini-scrollbar#91e1e566
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^16.8.4:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
@ -10149,10 +10256,10 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.2:
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
regenerator-transform@^0.14.0:
version "0.14.1"
@ -10508,9 +10615,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
inherits "^2.0.1"
rollup-plugin-terser@^5.1.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.2.0.tgz#ba758adf769347b7f1eaf9ef35978d2e207dccc7"
integrity sha512-jQI+nYhtDBc9HFRBz8iGttQg7li9klmzR62RG2W2nN6hJ/FI2K2ItYQ7kJ7/zn+vs+BP1AEccmVRjRN989I+Nw==
version "5.3.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz#9c0dd33d5771df9630cd027d6a2559187f65885e"
integrity sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==
dependencies:
"@babel/code-frame" "^7.5.5"
jest-worker "^24.9.0"
@ -10556,11 +10663,16 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
safe-buffer@^5.0.1, safe-buffer@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@ -10595,7 +10707,19 @@ sanitize-filename@^1.6.3:
dependencies:
truncate-utf8-bytes "^1.0.0"
sanitize-html@^1.18.4, sanitize-html@^1.19.1:
sanitize-html@^1.18.4:
version "1.25.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.25.0.tgz#e55ceac80ac6ea979f0a88400af4b9f67f938179"
integrity sha512-SyXESgCXWsclj/SyG53C1bmcOpmdcDYTCgr9nPE/Z9mUhTgj6d/wXDN1HS+U7ahf4DALgUPXSnm7d9koYimMdw==
dependencies:
chalk "^2.4.1"
htmlparser2 "^4.1.0"
lodash "^4.17.15"
postcss "^7.0.27"
srcset "^2.0.1"
xtend "^4.0.1"
sanitize-html@^1.19.1:
version "1.22.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.22.0.tgz#9df779c53cf5755adb2322943c21c1c1dffca7bf"
integrity sha512-3RPo65mbTKpOAdAYWU496MSty1YbB3Y5bjwL5OclgaSSMtv65xvM7RW/EHRumzaZ1UddEJowCbSdK0xl5sAu0A==
@ -10976,7 +11100,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12:
source-map-support@^0.5.16, source-map-support@^0.5.6:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
@ -10984,6 +11108,14 @@ source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.1
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@~0.5.12:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-url@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@ -11075,6 +11207,10 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
"sql.js@github:Kegsay/sql.js#d55d2069673d0a62ffe729a8a6c96b1328ae396c":
version "1.2.2"
resolved "https://codeload.github.com/Kegsay/sql.js/tar.gz/d55d2069673d0a62ffe729a8a6c96b1328ae396c"
srcset@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/srcset/-/srcset-2.0.1.tgz#8f842d357487eb797f413d9c309de7a5149df5ac"
@ -11580,7 +11716,7 @@ terser-webpack-plugin@^2.3.0:
terser "^4.4.3"
webpack-sources "^1.4.3"
terser@^4.1.2, terser@^4.4.3, terser@^4.6.2:
terser@^4.1.2, terser@^4.4.3:
version "4.6.4"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.4.tgz#40a0b37afbe5b57e494536815efa68326840fc00"
integrity sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==
@ -11589,6 +11725,15 @@ terser@^4.1.2, terser@^4.4.3, terser@^4.6.2:
source-map "~0.6.1"
source-map-support "~0.5.12"
terser@^4.6.2:
version "4.7.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006"
integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"
source-map-support "~0.5.12"
test-exclude@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0"
@ -11765,11 +11910,16 @@ truncate-utf8-bytes@^1.0.0:
dependencies:
utf8-byte-length "^1.0.1"
tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
tslib@^1.8.1, tslib@^1.9.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.0.tgz#f1f3528301621a53220d58373ae510ff747a66bc"
integrity sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==
tslib@^1.9.3:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
@ -12103,9 +12253,9 @@ url@^0.11.0:
querystring "0.2.0"
use-callback-ref@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.1.tgz#898759ccb9e14be6c7a860abafa3ffbd826c89bb"
integrity sha512-C3nvxh0ZpaOxs9RCnWwAJ+7bJPwQI8LHF71LzbQ3BvzH5XkdtlkMadqElGevg5bYBDFip4sAnD4m06zAKebg1w==
version "1.2.3"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.3.tgz#9f939dfb5740807bbf9dd79cdd4e99d27e827756"
integrity sha512-DPBPh1i2adCZoIArRlTuKRy7yue7QogtEnfv0AKrWsY+GA+4EKe37zhRDouNnyWMoNQFYZZRF+2dLHsWE4YvJA==
use-sidecar@^1.0.1:
version "1.0.2"
@ -12432,9 +12582,9 @@ websocket-extensions@>=0.1.1:
integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
what-input@^5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.6.tgz#ac6f003bf8d3592a0031dea7a03565469b00020b"
integrity sha512-a0BcI5YR7xp87vSzGcbN0IszJKpUQuTmrZaTSQBl7TLDIdKj6rDhluQ7b/7lYGG81gWDvkySsEvwv4BW5an9kg==
version "5.2.10"
resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.10.tgz#f79f5b65cf95d75e55e6d580bb0a6b98174cad4e"
integrity sha512-7AQoIMGq7uU8esmKniOtZG3A+pzlwgeyFpkS3f/yzRbxknSL68tvn5gjE6bZ4OMFxCPjpaBd2udUTqlZ0HwrXQ==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
version "1.0.5"
@ -12675,7 +12825,7 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^13.1.0, yargs-parser@^13.1.1:
yargs-parser@^13.1.0:
version "13.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
@ -12683,6 +12833,14 @@ yargs-parser@^13.1.0, yargs-parser@^13.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^13.1.1, yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^16.1.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1"
@ -12726,7 +12884,23 @@ yargs@13.2.4:
y18n "^4.0.0"
yargs-parser "^13.1.0"
yargs@^13.2.4, yargs@^13.3.0:
yargs@^13.2.4:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.2"
yargs@^13.3.0:
version "13.3.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==