forked from mystiq/hydrogen-web
Merge pull request #4 from bwindels/bwindels/lumia-fixes
Make what we have so far work on Lumia 950
This commit is contained in:
commit
56ae6670be
31 changed files with 827 additions and 104 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
node_modules
|
||||
bundle.js
|
||||
|
|
43
index-build-debug.html
Normal file
43
index-build-debug.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<meta name="application-name" content="Brawl Chat"/>
|
||||
<link rel="stylesheet" type="text/css" href="src/ui/web/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.DEBUG = true;
|
||||
let buf = "";
|
||||
console.error = (...params) => {
|
||||
const lastLines = "...\n" + buf.split("\n").slice(-10).join("\n");
|
||||
// buf = buf + "ERR " + params.join(" ") + "\n";
|
||||
// const location = new Error().stack.split("\n")[2];
|
||||
alert(params.join(" ") +"\n...\n" + lastLines);
|
||||
};
|
||||
console.log = console.info = console.warn = (...params) => {
|
||||
buf = buf + params.join(" ") + "\n";
|
||||
};
|
||||
main(document.body);
|
||||
setTimeout(() => {
|
||||
const showlogs = document.getElementById("showlogs");
|
||||
showlogs.addEventListener("click", () => {
|
||||
const lastLines = "...\n" + buf.split("\n").slice(-20).join("\n");
|
||||
alert(lastLines);
|
||||
}, true);
|
||||
showlogs.innerText = "Show last 20 log lines";
|
||||
}, 1000);
|
||||
// (async () => {
|
||||
// try {
|
||||
// const js = await (await fetch("bundle.js")).text();
|
||||
// eval(js+";main(document.body);");
|
||||
// } catch(err) {
|
||||
// alert(err.message);
|
||||
// }
|
||||
// })();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
13
index-build.html
Normal file
13
index-build.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="src/ui/web/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
main(document.body);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -8,7 +8,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/impunity --entryPoint src/main.js --force-esm",
|
||||
"start": "node scripts/serve-local.js"
|
||||
"start": "node scripts/serve-local.js",
|
||||
"build": "node_modules/rollup/bin/rollup -c"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -23,6 +24,7 @@
|
|||
"devDependencies": {
|
||||
"finalhandler": "^1.1.1",
|
||||
"impunity": "^0.0.7",
|
||||
"rollup": "^1.15.6",
|
||||
"serve-static": "^1.13.2"
|
||||
}
|
||||
}
|
||||
|
|
30
prototypes/base256.html
Normal file
30
prototypes/base256.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function encodeNumber(n) {
|
||||
const a = (n & 0xFFFF);
|
||||
const b = (n & 0xFFFF0000) >> 16;
|
||||
const c = (n & 0xFFFF00000000) >> 32;
|
||||
const d = (n & 0xFFFF000000000000) >> 48;
|
||||
return String.fromCharCode(a, b, c, d);
|
||||
}
|
||||
|
||||
function printN(n) {
|
||||
//document.write(`<p>fn(${n}) = ${encodeNumber(n)}</p>`);
|
||||
console.log(n, encodeNumber(n));
|
||||
}
|
||||
|
||||
printN(0);
|
||||
printN(9);
|
||||
printN(1000);
|
||||
printN(Number.MAX_SAFE_INTEGER);
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
110
prototypes/idb-cmp-numbers.html
Normal file
110
prototypes/idb-cmp-numbers.html
Normal file
|
@ -0,0 +1,110 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
pre {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
font-size: 2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 0xFFFFFFFF;
|
||||
|
||||
function zeropad(a, n) {
|
||||
return "0".repeat(n - a.length) + a;
|
||||
}
|
||||
|
||||
// const encodeNumber = n => zeropad((n >>> 0).toString(16), 8);
|
||||
|
||||
function encodeNumber(n) {
|
||||
const a = (n & 0xFFFF0000) >>> 16;
|
||||
const b = (n & 0xFFFF) >>> 0;
|
||||
//return zeropad(a.toString(16), 4) + zeropad(b.toString(16), 4);
|
||||
return String.fromCharCode(a, b);
|
||||
}
|
||||
|
||||
function decodeNumber(s) {
|
||||
const a = s.charCodeAt(0);
|
||||
const b = s.charCodeAt(1);
|
||||
//return `${a.toString(16)} ${b}`;
|
||||
return ((a << 16) | b) >>> 0;
|
||||
}
|
||||
|
||||
function formatArg(a) {
|
||||
if (typeof a === "string") {
|
||||
return `"${a}"`;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
return `[${a.map(formatArg)}]`;
|
||||
}
|
||||
return a+"";
|
||||
}
|
||||
function cmp(a, b) {
|
||||
let value;
|
||||
try {
|
||||
const result = indexedDB.cmp(encodeNumber(a), encodeNumber(b));
|
||||
if (result < 0) {
|
||||
value = "a < b";
|
||||
} else if (result === 0) {
|
||||
value = "a = b";
|
||||
} else if (result > 0) {
|
||||
value = "a > b";
|
||||
}
|
||||
} catch(err) {
|
||||
value = err.message;
|
||||
}
|
||||
return `cmp(${formatArg(a)} as ${formatArg(encodeNumber(a))},\n ${formatArg(b)} as ${formatArg(encodeNumber(b))}): ${value}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const tests = [
|
||||
// see https://stackoverflow.com/questions/28413947/space-efficient-way-to-encode-numbers-as-sortable-strings
|
||||
// need to encode numbers with base 256 and zero padded at start
|
||||
// should still fit in 8 bytes then?
|
||||
(cmp) => cmp(9, 100000),
|
||||
(cmp) => cmp(1, 2),
|
||||
(cmp) => cmp(MIN, 1),
|
||||
(cmp) => cmp(MIN, MIN),
|
||||
(cmp) => cmp(MIN, MAX),
|
||||
(cmp) => cmp(MAX >>> 1, MAX),
|
||||
(cmp) => cmp(0x7fffffff, 0xffff7fff),
|
||||
(cmp) => cmp(MAX, MAX),
|
||||
(cmp) => cmp(MAX - 1, MAX),
|
||||
];
|
||||
|
||||
for (const fn of tests) {
|
||||
const txt = document.createTextNode(fn(cmp));
|
||||
const p = document.createElement("pre");
|
||||
p.appendChild(txt);
|
||||
document.body.appendChild(p);
|
||||
}
|
||||
} catch(err) {
|
||||
alert(err.message);
|
||||
}
|
||||
|
||||
let prev;
|
||||
for (let i = MIN; i <= MAX; i += 100) {
|
||||
if (decodeNumber(encodeNumber(i)) !== i) {
|
||||
document.write(`<p>${i} decodes back to ${decodeNumber(encodeNumber(i))}</p>`);
|
||||
break;
|
||||
}
|
||||
if (typeof prev === "number") {
|
||||
if (indexedDB.cmp(encodeNumber(prev), encodeNumber(i)) >= 0) {
|
||||
document.write(`<p>${i} <= ${prev}</p>`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
prev = i;
|
||||
}
|
||||
document.write(`<p>check from ${MIN} to ${prev}</p>`);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
76
prototypes/idb-cmp.html
Normal file
76
prototypes/idb-cmp.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
pre {
|
||||
font-family: "courier";
|
||||
display: block;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function encodeNumber(n) {
|
||||
const a = (n & 0xFFFF);
|
||||
const b = (n & 0xFFFF0000) >> 16;
|
||||
const c = (n & 0xFFFF00000000) >> 32;
|
||||
const d = (n & 0xFFFF000000000000) >> 48;
|
||||
return String.fromCharCode(a, b, c, d);
|
||||
}
|
||||
function formatArg(a) {
|
||||
if (typeof a === "string") {
|
||||
return `"${a}"`;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
return `[${a.map(formatArg)}]`;
|
||||
}
|
||||
return a+"";
|
||||
}
|
||||
function cmp(a, b) {
|
||||
let value;
|
||||
try {
|
||||
const result = indexedDB.cmp(encodeNumber(a), encodeNumber(b));
|
||||
if (result < 0) {
|
||||
value = "a < b";
|
||||
} else if (result === 0) {
|
||||
value = "a = b";
|
||||
} else if (result > 0) {
|
||||
value = "a > b";
|
||||
}
|
||||
} catch(err) {
|
||||
value = err.message;
|
||||
}
|
||||
return `cmp(${formatArg(a)},\n ${formatArg(b)}): ${value}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const tests = [
|
||||
(cmp) => cmp(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER),
|
||||
(cmp) => cmp([Number.MIN_SAFE_INTEGER], [Number.MAX_SAFE_INTEGER]),
|
||||
// see https://stackoverflow.com/questions/28413947/space-efficient-way-to-encode-numbers-as-sortable-strings
|
||||
// need to encode numbers with base 256 and zero padded at start
|
||||
// should still fit in 8 bytes then?
|
||||
(cmp) => cmp("foo-9", "foo-10000"),
|
||||
(cmp) => cmp("foo-\u0000", "foo-\uFFFF"),
|
||||
(cmp) => cmp("foo-\u0000", "foo-0"),
|
||||
(cmp) => cmp("foo-" + Number.MAX_SAFE_INTEGER, "foo-\uFFFF"),
|
||||
(cmp) => cmp("!abc:host.tld,"+Number.MIN_SAFE_INTEGER, "!abc:host.tld,"+(Number.MIN_SAFE_INTEGER + 1)),
|
||||
(cmp) => cmp("!abc:host.tld,"+0, "!abc:host.tld,"+(Number.MAX_SAFE_INTEGER)),
|
||||
(cmp) => cmp("!abc:host.tld,"+Math.floor(Number.MAX_SAFE_INTEGER / 2), "!abc:host.tld,"+(Number.MAX_SAFE_INTEGER)),
|
||||
];
|
||||
|
||||
for (const fn of tests) {
|
||||
const txt = document.createTextNode(fn(cmp));
|
||||
const p = document.createElement("pre");
|
||||
p.appendChild(txt);
|
||||
document.body.appendChild(p);
|
||||
}
|
||||
} catch(err) {
|
||||
alert(err.message);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
<head><meta charset="utf-8"></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
console.log = (...params) => {
|
||||
document.write(params.join(" ")+"<br>");
|
||||
};
|
||||
|
||||
function reqAsPromise(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => resolve(req);
|
||||
|
|
75
prototypes/idb-multi-key.html
Normal file
75
prototypes/idb-multi-key.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function reqAsPromise(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => resolve(req);
|
||||
req.onerror = (err) => reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
class Storage {
|
||||
constructor(databaseName) {
|
||||
this._databaseName = databaseName;
|
||||
this._database = null;
|
||||
}
|
||||
|
||||
async open() {
|
||||
const req = window.indexedDB.open(this._databaseName);
|
||||
req.onupgradeneeded = (ev) => {
|
||||
const db = ev.target.result;
|
||||
const oldVersion = ev.oldVersion;
|
||||
this._createStores(db, oldVersion);
|
||||
};
|
||||
await reqAsPromise(req);
|
||||
this._database = req.result;
|
||||
}
|
||||
|
||||
_createStores(db) {
|
||||
db.createObjectStore("files", {keyPath: ["idName"]});
|
||||
}
|
||||
|
||||
async storeFoo(id, name) {
|
||||
const tx = this._database.transaction(["files"], "readwrite");
|
||||
const store = tx.objectStore("files");
|
||||
await reqAsPromise(store.add(value(id, name)));
|
||||
}
|
||||
}
|
||||
|
||||
function value(id, name) {
|
||||
return {idName: key(id, name)};
|
||||
}
|
||||
|
||||
function key(id, name) {
|
||||
return id+","+name;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let storage = new Storage("idb-multi-key2");
|
||||
try {
|
||||
await storage.open();
|
||||
await storage.storeFoo(5, "foo");
|
||||
await storage.storeFoo(6, "bar");
|
||||
alert("all good");
|
||||
} catch(err) {
|
||||
alert(err.message);
|
||||
}
|
||||
try {
|
||||
const result = indexedDB.cmp(key(5, "foo"), key(6, "bar"));
|
||||
//IDBKeyRange.bound(["aaa", "111"],["zzz", "999"], false, false);
|
||||
alert("all good: " + result);
|
||||
} catch (err) {
|
||||
alert(`IDBKeyRange.bound: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
8
rollup.config.js
Normal file
8
rollup.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
input: 'src/main.js',
|
||||
output: {
|
||||
file: 'bundle.js',
|
||||
format: 'iife',
|
||||
name: 'main'
|
||||
}
|
||||
};
|
5
src/Platform.js
Normal file
5
src/Platform.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// #ifdef PLATFORM_GNOME
|
||||
// export {default} from "./ui/gnome/GnomePlatform.js";
|
||||
// #else
|
||||
export {default} from "./ui/web/WebPlatform.js";
|
||||
// #endif
|
|
@ -5,10 +5,10 @@ import Sync from "./matrix/sync.js";
|
|||
import SessionView from "./ui/web/session/SessionView.js";
|
||||
import SessionViewModel from "./domain/session/SessionViewModel.js";
|
||||
|
||||
const HOST = "localhost";
|
||||
const HOST = "192.168.2.108";
|
||||
const HOMESERVER = `http://${HOST}:8008`;
|
||||
const USERNAME = "bruno1";
|
||||
const USER_ID = `@${USERNAME}:${HOST}`;
|
||||
const USER_ID = `@${USERNAME}:localhost`;
|
||||
const PASSWORD = "testtest";
|
||||
|
||||
function getSessionInfo(userId) {
|
||||
|
@ -50,7 +50,6 @@ function showSession(container, session, sync) {
|
|||
container.appendChild(view.mount());
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export default async function main(container) {
|
||||
try {
|
||||
let sessionInfo = getSessionInfo(USER_ID);
|
||||
|
|
|
@ -5,9 +5,6 @@ export class HomeServerError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class StorageError extends Error {
|
||||
}
|
||||
|
||||
export class RequestAbortError extends Error {
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,21 @@ import {
|
|||
|
||||
class RequestWrapper {
|
||||
constructor(promise, controller) {
|
||||
this._promise = promise;
|
||||
this._controller = controller;
|
||||
if (!controller) {
|
||||
const abortPromise = new Promise((_, reject) => {
|
||||
this._controller = {
|
||||
abort() {
|
||||
const err = new Error("fetch request aborted");
|
||||
err.name = "AbortError";
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
});
|
||||
this._promise = Promise.race([promise, abortPromise]);
|
||||
} else {
|
||||
this._promise = promise;
|
||||
this._controller = controller;
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
|
@ -47,13 +60,13 @@ export default class HomeServerApi {
|
|||
headers.append("Content-Type", "application/json");
|
||||
bodyString = JSON.stringify(body);
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
||||
// TODO: set authenticated headers with second arguments, cache them
|
||||
let promise = fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: bodyString,
|
||||
signal: controller.signal
|
||||
signal: controller && controller.signal
|
||||
});
|
||||
promise = promise.then(async (response) => {
|
||||
if (response.ok) {
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
const DEFAULT_LIVE_FRAGMENT_ID = 0;
|
||||
const MIN_EVENT_INDEX = Number.MIN_SAFE_INTEGER + 1;
|
||||
const MAX_EVENT_INDEX = Number.MAX_SAFE_INTEGER - 1;
|
||||
const MID_EVENT_INDEX = 0;
|
||||
import Platform from "../../../Platform.js";
|
||||
|
||||
// key for events in the timelineEvents store
|
||||
export default class EventKey {
|
||||
|
@ -12,7 +9,7 @@ export default class EventKey {
|
|||
|
||||
nextFragmentKey() {
|
||||
// could take MIN_EVENT_INDEX here if it can't be paged back
|
||||
return new EventKey(this.fragmentId + 1, MID_EVENT_INDEX);
|
||||
return new EventKey(this.fragmentId + 1, Platform.middleStorageKey);
|
||||
}
|
||||
|
||||
nextKeyForDirection(direction) {
|
||||
|
@ -32,15 +29,15 @@ export default class EventKey {
|
|||
}
|
||||
|
||||
static get maxKey() {
|
||||
return new EventKey(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
||||
return new EventKey(Platform.maxStorageKey, Platform.maxStorageKey);
|
||||
}
|
||||
|
||||
static get minKey() {
|
||||
return new EventKey(Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
||||
return new EventKey(Platform.minStorageKey, Platform.minStorageKey);
|
||||
}
|
||||
|
||||
static get defaultLiveKey() {
|
||||
return new EventKey(DEFAULT_LIVE_FRAGMENT_ID, MID_EVENT_INDEX);
|
||||
return new EventKey(Platform.minStorageKey, Platform.middleStorageKey);
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import BaseEntry from "./BaseEntry.js";
|
||||
import Direction from "../Direction.js";
|
||||
import {isValidFragmentId} from "../common.js";
|
||||
import Platform from "../../../../Platform.js";
|
||||
|
||||
export default class FragmentBoundaryEntry extends BaseEntry {
|
||||
constructor(fragment, isFragmentStart, fragmentIdComparer) {
|
||||
|
@ -36,9 +37,9 @@ export default class FragmentBoundaryEntry extends BaseEntry {
|
|||
|
||||
get entryIndex() {
|
||||
if (this.started) {
|
||||
return Number.MIN_SAFE_INTEGER;
|
||||
return Platform.minStorageKey;
|
||||
} else {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
return Platform.maxStorageKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {directionalConcat, directionalAppend} from "./common.js";
|
||||
import EventKey from "../EventKey.js";
|
||||
import Direction from "../Direction.js";
|
||||
import EventEntry from "../entries/EventEntry.js";
|
||||
import FragmentBoundaryEntry from "../entries/FragmentBoundaryEntry.js";
|
||||
|
|
|
@ -4,3 +4,17 @@ export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => {
|
|||
nameMap[name] = name;
|
||||
return nameMap;
|
||||
}, {}));
|
||||
|
||||
export class StorageError extends Error {
|
||||
constructor(message, cause) {
|
||||
let fullMessage = message;
|
||||
if (cause) {
|
||||
fullMessage += ": ";
|
||||
if (cause.name) {
|
||||
fullMessage += `(${cause.name}) `;
|
||||
}
|
||||
fullMessage += cause.message;
|
||||
}
|
||||
super(fullMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,14 @@ function createStores(db) {
|
|||
db.createObjectStore("roomSummary", {keyPath: "roomId"});
|
||||
|
||||
// need index to find live fragment? prooobably ok without for now
|
||||
db.createObjectStore("timelineFragments", {keyPath: ["roomId", "id"]});
|
||||
const timelineEvents = db.createObjectStore("timelineEvents", {keyPath: ["roomId", "fragmentId", "eventIndex"]});
|
||||
timelineEvents.createIndex("byEventId", [
|
||||
"roomId",
|
||||
"event.event_id"
|
||||
], {unique: true});
|
||||
|
||||
db.createObjectStore("roomState", {keyPath: [
|
||||
"roomId",
|
||||
"event.type",
|
||||
"event.state_key"
|
||||
]});
|
||||
//key = room_id | fragment_id
|
||||
db.createObjectStore("timelineFragments", {keyPath: "key"});
|
||||
//key = room_id | fragment_id | event_index
|
||||
const timelineEvents = db.createObjectStore("timelineEvents", {keyPath: "key"});
|
||||
//eventIdKey = room_id | event_id
|
||||
timelineEvents.createIndex("byEventId", "eventIdKey", {unique: true});
|
||||
//key = room_id | event.type | event.state_key,
|
||||
db.createObjectStore("roomState", {keyPath: "key"});
|
||||
|
||||
// const roomMembers = db.createObjectStore("roomMembers", {keyPath: [
|
||||
// "event.room_id",
|
||||
|
|
|
@ -5,6 +5,18 @@ export default class QueryTarget {
|
|||
this._target = target;
|
||||
}
|
||||
|
||||
_openCursor(range, direction) {
|
||||
if (range && direction) {
|
||||
return this._target.openCursor(range, direction);
|
||||
} else if (range) {
|
||||
return this._target.openCursor(range);
|
||||
} else if (direction) {
|
||||
return this._target.openCursor(null, direction);
|
||||
} else {
|
||||
return this._target.openCursor();
|
||||
}
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return reqAsPromise(this._target.get(key));
|
||||
}
|
||||
|
@ -34,7 +46,7 @@ export default class QueryTarget {
|
|||
}
|
||||
|
||||
async selectAll(range, direction) {
|
||||
const cursor = this._target.openCursor(range, direction);
|
||||
const cursor = this._openCursor(range, direction);
|
||||
const results = [];
|
||||
await iterateCursor(cursor, (value) => {
|
||||
results.push(value);
|
||||
|
@ -97,7 +109,7 @@ export default class QueryTarget {
|
|||
|
||||
_reduce(range, reducer, initialValue, direction) {
|
||||
let reducedValue = initialValue;
|
||||
const cursor = this._target.openCursor(range, direction);
|
||||
const cursor = this._openCursor(range, direction);
|
||||
return iterateCursor(cursor, (value) => {
|
||||
reducedValue = reducer(reducedValue, value);
|
||||
return {done: false};
|
||||
|
@ -111,7 +123,7 @@ export default class QueryTarget {
|
|||
}
|
||||
|
||||
async _selectWhile(range, predicate, direction) {
|
||||
const cursor = this._target.openCursor(range, direction);
|
||||
const cursor = this._openCursor(range, direction);
|
||||
const results = [];
|
||||
await iterateCursor(cursor, (value) => {
|
||||
results.push(value);
|
||||
|
@ -121,7 +133,7 @@ export default class QueryTarget {
|
|||
}
|
||||
|
||||
async _find(range, predicate, direction) {
|
||||
const cursor = this._target.openCursor(range, direction);
|
||||
const cursor = this._openCursor(range, direction);
|
||||
let result;
|
||||
const found = await iterateCursor(cursor, (value) => {
|
||||
const found = predicate(value);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Transaction from "./transaction.js";
|
||||
import { STORE_NAMES } from "../common.js";
|
||||
import { STORE_NAMES, StorageError } from "../common.js";
|
||||
|
||||
export default class Storage {
|
||||
constructor(idbDatabase) {
|
||||
|
@ -14,19 +14,27 @@ export default class Storage {
|
|||
_validateStoreNames(storeNames) {
|
||||
const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name));
|
||||
if (idx !== -1) {
|
||||
throw new Error(`Tried to open a transaction for unknown store ${storeNames[idx]}`);
|
||||
throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`);
|
||||
}
|
||||
}
|
||||
|
||||
async readTxn(storeNames) {
|
||||
this._validateStoreNames(storeNames);
|
||||
const txn = this._db.transaction(storeNames, "readonly");
|
||||
return new Transaction(txn, storeNames);
|
||||
try {
|
||||
const txn = this._db.transaction(storeNames, "readonly");
|
||||
return new Transaction(txn, storeNames);
|
||||
} catch(err) {
|
||||
throw new StorageError("readTxn failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
async readWriteTxn(storeNames) {
|
||||
this._validateStoreNames(storeNames);
|
||||
const txn = this._db.transaction(storeNames, "readwrite");
|
||||
return new Transaction(txn, storeNames);
|
||||
try {
|
||||
const txn = this._db.transaction(storeNames, "readwrite");
|
||||
return new Transaction(txn, storeNames);
|
||||
} catch(err) {
|
||||
throw new StorageError("readWriteTxn failed", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,64 @@
|
|||
import QueryTarget from "./query-target.js";
|
||||
import { reqAsPromise } from "./utils.js";
|
||||
import { StorageError } from "../common.js";
|
||||
|
||||
class QueryTargetWrapper {
|
||||
constructor(qt) {
|
||||
this._qt = qt;
|
||||
}
|
||||
|
||||
openKeyCursor(...params) {
|
||||
try {
|
||||
return this._qt.openKeyCursor(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("openKeyCursor failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
openCursor(...params) {
|
||||
try {
|
||||
return this._qt.openCursor(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("openCursor failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
put(...params) {
|
||||
try {
|
||||
return this._qt.put(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("put failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
add(...params) {
|
||||
try {
|
||||
return this._qt.add(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("add failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
get(...params) {
|
||||
try {
|
||||
return this._qt.get(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("get failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
index(...params) {
|
||||
try {
|
||||
return this._qt.index(...params);
|
||||
} catch(err) {
|
||||
throw new StorageError("index failed", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Store extends QueryTarget {
|
||||
constructor(idbStore) {
|
||||
super(idbStore);
|
||||
super(new QueryTargetWrapper(idbStore));
|
||||
}
|
||||
|
||||
get _idbStore() {
|
||||
|
@ -11,7 +66,7 @@ export default class Store extends QueryTarget {
|
|||
}
|
||||
|
||||
index(indexName) {
|
||||
return new QueryTarget(this._idbStore.index(indexName));
|
||||
return new QueryTarget(new QueryTargetWrapper(this._idbStore.index(indexName)));
|
||||
}
|
||||
|
||||
put(value) {
|
||||
|
|
|
@ -12,6 +12,8 @@ export default class RoomStateStore {
|
|||
}
|
||||
|
||||
async setStateEvent(roomId, event) {
|
||||
return this._roomStateStore.put({roomId, event});
|
||||
const key = `${roomId}|${event.type}|${event.state_key}`;
|
||||
const entry = {roomId, event, key};
|
||||
return this._roomStateStore.put(entry);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,25 @@
|
|||
import EventKey from "../../../room/timeline/EventKey.js";
|
||||
import { StorageError } from "../../common.js";
|
||||
import Platform from "../../../../Platform.js";
|
||||
|
||||
// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb
|
||||
function encodeUint32(n) {
|
||||
const hex = n.toString(16);
|
||||
return "0".repeat(8 - hex.length) + hex;
|
||||
}
|
||||
|
||||
function encodeKey(roomId, fragmentId, eventIndex) {
|
||||
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
|
||||
}
|
||||
|
||||
function encodeEventIdKey(roomId, eventId) {
|
||||
return `${roomId}|${eventId}`;
|
||||
}
|
||||
|
||||
function decodeEventIdKey(eventIdKey) {
|
||||
const [roomId, eventId] = eventIdKey.split("|");
|
||||
return {roomId, eventId};
|
||||
}
|
||||
|
||||
class Range {
|
||||
constructor(only, lower, upper, lowerOpen, upperOpen) {
|
||||
|
@ -10,38 +31,42 @@ class Range {
|
|||
}
|
||||
|
||||
asIDBKeyRange(roomId) {
|
||||
// only
|
||||
if (this._only) {
|
||||
return IDBKeyRange.only([roomId, this._only.fragmentId, this._only.eventIndex]);
|
||||
}
|
||||
// lowerBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (this._lower && !this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, this._lower.fragmentId, this._lower.eventIndex],
|
||||
[roomId, this._lower.fragmentId, EventKey.maxKey.eventIndex],
|
||||
this._lowerOpen,
|
||||
false
|
||||
);
|
||||
}
|
||||
// upperBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (!this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, this._upper.fragmentId, EventKey.minKey.eventIndex],
|
||||
[roomId, this._upper.fragmentId, this._upper.eventIndex],
|
||||
false,
|
||||
this._upperOpen
|
||||
);
|
||||
}
|
||||
// bound
|
||||
if (this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, this._lower.fragmentId, this._lower.eventIndex],
|
||||
[roomId, this._upper.fragmentId, this._upper.eventIndex],
|
||||
this._lowerOpen,
|
||||
this._upperOpen
|
||||
);
|
||||
try {
|
||||
// only
|
||||
if (this._only) {
|
||||
return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex));
|
||||
}
|
||||
// lowerBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (this._lower && !this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
|
||||
encodeKey(roomId, this._lower.fragmentId, Platform.maxStorageKey),
|
||||
this._lowerOpen,
|
||||
false
|
||||
);
|
||||
}
|
||||
// upperBound
|
||||
// also bound as we don't want to move into another roomId
|
||||
if (!this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._upper.fragmentId, Platform.minStorageKey),
|
||||
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
|
||||
false,
|
||||
this._upperOpen
|
||||
);
|
||||
}
|
||||
// bound
|
||||
if (this._lower && this._upper) {
|
||||
return IDBKeyRange.bound(
|
||||
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
|
||||
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
|
||||
this._lowerOpen,
|
||||
this._upperOpen
|
||||
);
|
||||
}
|
||||
} catch(err) {
|
||||
throw new StorageError(`IDBKeyRange failed with data: ` + JSON.stringify(this), err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +194,7 @@ export default class TimelineEventStore {
|
|||
// also passing them in chronological order makes sense as that's how we'll receive them almost always.
|
||||
async findFirstOccurringEventId(roomId, eventIds) {
|
||||
const byEventId = this._timelineStore.index("byEventId");
|
||||
const keys = eventIds.map(eventId => [roomId, eventId]);
|
||||
const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId));
|
||||
const results = new Array(keys.length);
|
||||
let firstFoundKey;
|
||||
|
||||
|
@ -190,8 +215,7 @@ export default class TimelineEventStore {
|
|||
firstFoundKey = firstFoundAndPrecedingResolved();
|
||||
return !!firstFoundKey;
|
||||
});
|
||||
// key of index is [roomId, eventId], so pick out eventId
|
||||
return firstFoundKey && firstFoundKey[1];
|
||||
return firstFoundKey && decodeEventIdKey(firstFoundKey).eventId;
|
||||
}
|
||||
|
||||
/** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown.
|
||||
|
@ -200,6 +224,8 @@ export default class TimelineEventStore {
|
|||
* @throws {StorageError} ...
|
||||
*/
|
||||
insert(entry) {
|
||||
entry.key = encodeKey(entry.roomId, entry.fragmentId, entry.eventIndex);
|
||||
entry.eventIdKey = encodeEventIdKey(entry.roomId, entry.event.event_id);
|
||||
// TODO: map error? or in idb/store?
|
||||
return this._timelineStore.add(entry);
|
||||
}
|
||||
|
@ -214,7 +240,7 @@ export default class TimelineEventStore {
|
|||
}
|
||||
|
||||
get(roomId, eventKey) {
|
||||
return this._timelineStore.get([roomId, eventKey.fragmentId, eventKey.eventIndex]);
|
||||
return this._timelineStore.get(encodeKey(roomId, eventKey.fragmentId, eventKey.eventIndex));
|
||||
}
|
||||
// returns the entries as well!! (or not always needed? I guess not always needed, so extra method)
|
||||
removeRange(roomId, range) {
|
||||
|
@ -223,6 +249,6 @@ export default class TimelineEventStore {
|
|||
}
|
||||
|
||||
getByEventId(roomId, eventId) {
|
||||
return this._timelineStore.index("byEventId").get([roomId, eventId]);
|
||||
return this._timelineStore.index("byEventId").get(encodeEventIdKey(roomId, eventId));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
import { StorageError } from "../../common.js";
|
||||
import Platform from "../../../../Platform.js";
|
||||
|
||||
function encodeKey(roomId, fragmentId) {
|
||||
let fragmentIdHex = fragmentId.toString(16);
|
||||
fragmentIdHex = "0".repeat(8 - fragmentIdHex.length) + fragmentIdHex;
|
||||
return `${roomId}|${fragmentIdHex}`;
|
||||
}
|
||||
|
||||
export default class RoomFragmentStore {
|
||||
constructor(store) {
|
||||
this._store = store;
|
||||
}
|
||||
|
||||
_allRange(roomId) {
|
||||
return IDBKeyRange.bound(
|
||||
[roomId, Number.MIN_SAFE_INTEGER],
|
||||
[roomId, Number.MAX_SAFE_INTEGER]
|
||||
);
|
||||
try {
|
||||
return IDBKeyRange.bound(
|
||||
encodeKey(roomId, Platform.minStorageKey),
|
||||
encodeKey(roomId, Platform.maxStorageKey)
|
||||
);
|
||||
} catch (err) {
|
||||
throw new StorageError(`error from IDBKeyRange with roomId ${roomId}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
all(roomId) {
|
||||
|
@ -33,6 +46,7 @@ export default class RoomFragmentStore {
|
|||
// depends if we want to do anything smart with fragment ids,
|
||||
// like give them meaning depending on range. not for now probably ...
|
||||
add(fragment) {
|
||||
fragment.key = encodeKey(fragment.roomId, fragment.id);
|
||||
return this._store.add(fragment);
|
||||
}
|
||||
|
||||
|
@ -41,6 +55,6 @@ export default class RoomFragmentStore {
|
|||
}
|
||||
|
||||
get(roomId, fragmentId) {
|
||||
return this._store.get([roomId, fragmentId]);
|
||||
return this._store.get(encodeKey(roomId, fragmentId));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {txnAsPromise} from "./utils.js";
|
||||
import {StorageError} from "../common.js";
|
||||
import Store from "./store.js";
|
||||
import SessionStore from "./stores/SessionStore.js";
|
||||
import RoomSummaryStore from "./stores/RoomSummaryStore.js";
|
||||
|
@ -21,7 +22,7 @@ export default class Transaction {
|
|||
_idbStore(name) {
|
||||
if (!this._allowedStoreNames.includes(name)) {
|
||||
// more specific error? this is a bug, so maybe not ...
|
||||
throw new Error(`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));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { StorageError } from "../common.js";
|
||||
|
||||
export function openDatabase(name, createObjectStore, version) {
|
||||
const req = window.indexedDB.open(name, version);
|
||||
req.onupgradeneeded = (ev) => {
|
||||
|
@ -8,17 +10,21 @@ export function openDatabase(name, createObjectStore, version) {
|
|||
return reqAsPromise(req);
|
||||
}
|
||||
|
||||
function wrapError(err) {
|
||||
return new StorageError(`wrapped DOMException`, err);
|
||||
}
|
||||
|
||||
export function reqAsPromise(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.addEventListener("success", event => resolve(event.target.result));
|
||||
req.addEventListener("error", event => reject(event.target.error));
|
||||
req.addEventListener("error", event => reject(wrapError(event.target.error)));
|
||||
});
|
||||
}
|
||||
|
||||
export function txnAsPromise(txn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.addEventListener("complete", resolve);
|
||||
txn.addEventListener("abort", reject);
|
||||
txn.addEventListener("abort", e => reject(wrapError(e)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -38,10 +44,14 @@ export function iterateCursor(cursor, processValue) {
|
|||
const {done, jumpTo} = processValue(cursor.value, cursor.key);
|
||||
if (done) {
|
||||
resolve(true);
|
||||
} else {
|
||||
} else if(jumpTo) {
|
||||
cursor.continue(jumpTo);
|
||||
} else {
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
}).catch(err => {
|
||||
throw new StorageError("iterateCursor failed", err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -97,7 +107,7 @@ export async function findStoreValue(db, storeName, toCursor, matchesValue) {
|
|||
}
|
||||
});
|
||||
if (!matched) {
|
||||
throw new Error("Value not found");
|
||||
throw new StorageError("Value not found");
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
RequestAbortError,
|
||||
HomeServerError,
|
||||
StorageError
|
||||
} from "./error.js";
|
||||
import {RequestAbortError} from "./error.js";
|
||||
import EventEmitter from "../EventEmitter.js";
|
||||
|
||||
const INCREMENTAL_TIMEOUT = 30000;
|
||||
|
@ -111,7 +107,8 @@ export default class Sync extends EventEmitter {
|
|||
await syncTxn.complete();
|
||||
console.info("syncTxn committed!!");
|
||||
} catch (err) {
|
||||
throw new StorageError("unable to commit sync tranaction", err);
|
||||
console.error("unable to commit sync tranaction", err.message);
|
||||
throw err;
|
||||
}
|
||||
// emit room related events after txn has been closed
|
||||
for(let {room, changes} of roomChanges) {
|
||||
|
|
16
src/ui/web/WebPlatform.js
Normal file
16
src/ui/web/WebPlatform.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
export default {
|
||||
get minStorageKey() {
|
||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||
return 0;
|
||||
},
|
||||
|
||||
get middleStorageKey() {
|
||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||
return 0x7FFFFFFF;
|
||||
},
|
||||
|
||||
get maxStorageKey() {
|
||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||
return 0xFFFFFFFF;
|
||||
},
|
||||
}
|
|
@ -11,7 +11,8 @@ export default class SyncStatusBar extends TemplateView {
|
|||
"SyncStatusBar_shown": true,
|
||||
}}, [
|
||||
vm => vm.status,
|
||||
t.if(vm => !vm.isSyncing, t => t.button({onClick: () => vm.trySync()}, "Try syncing"))
|
||||
t.if(vm => !vm.isSyncing, t => t.button({onClick: () => vm.trySync()}, "Try syncing")),
|
||||
window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : ""
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
197
yarn.lock
Normal file
197
yarn.lock
Normal file
|
@ -0,0 +1,197 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||
|
||||
"@types/node@^12.0.8":
|
||||
version "12.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.8.tgz#551466be11b2adc3f3d47156758f610bd9f6b1d8"
|
||||
integrity sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==
|
||||
|
||||
acorn@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
|
||||
integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
|
||||
|
||||
colors@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
|
||||
integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
|
||||
|
||||
commander@^2.19.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
finalhandler@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.3"
|
||||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
http-errors@~1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
impunity@^0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/impunity/-/impunity-0.0.7.tgz#15b8aaafcc98dcf3a4b5bcfa0beea4eea63d760f"
|
||||
integrity sha512-+DhzXSWrzqI1KNroKt3y1LkLTn/aoJpt4DzxWN+hair+Jfb+iJAbTEsSFkYUG7kASP9TF9GvI0hIBUul6PjpKg==
|
||||
dependencies:
|
||||
colors "^1.3.3"
|
||||
commander "^2.19.0"
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
rollup@^1.15.6:
|
||||
version "1.15.6"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.15.6.tgz#caf0ed28d2d78e3a59c1398e5a3695fb600a0ef0"
|
||||
integrity sha512-s3Vn3QJQ5YVFfIG4nXoG9VdL1I37IZsft+4ZyeBhxE0df1kCFz9e+4bEAbR4mKH3pvBO9e9xjdxWPhhIp0r9ow==
|
||||
dependencies:
|
||||
"@types/estree" "0.0.39"
|
||||
"@types/node" "^12.0.8"
|
||||
acorn "^6.1.1"
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
destroy "~1.0.4"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "~1.7.2"
|
||||
mime "1.6.0"
|
||||
ms "2.1.1"
|
||||
on-finished "~2.3.0"
|
||||
range-parser "~1.2.1"
|
||||
statuses "~1.5.0"
|
||||
|
||||
serve-static@^1.13.2:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.3"
|
||||
send "0.17.1"
|
||||
|
||||
setprototypeof@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
|
||||
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
Loading…
Reference in a new issue