forked from mystiq/hydrogen-web
convert tabs to spaces where needed
This commit is contained in:
parent
56ae6670be
commit
cd9f25ea80
10 changed files with 340 additions and 340 deletions
|
@ -1,36 +1,36 @@
|
||||||
export default class EventEmitter {
|
export default class EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._handlersByName = {};
|
this._handlersByName = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(name, ...values) {
|
emit(name, ...values) {
|
||||||
const handlers = this._handlersByName[name];
|
const handlers = this._handlersByName[name];
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
for(const h of handlers) {
|
for(const h of handlers) {
|
||||||
h(...values);
|
h(...values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on(name, callback) {
|
on(name, callback) {
|
||||||
let handlers = this._handlersByName[name];
|
let handlers = this._handlersByName[name];
|
||||||
if (!handlers) {
|
if (!handlers) {
|
||||||
this.onFirstSubscriptionAdded(name);
|
this.onFirstSubscriptionAdded(name);
|
||||||
this._handlersByName[name] = handlers = new Set();
|
this._handlersByName[name] = handlers = new Set();
|
||||||
}
|
}
|
||||||
handlers.add(callback);
|
handlers.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
off(name, callback) {
|
off(name, callback) {
|
||||||
const handlers = this._handlersByName[name];
|
const handlers = this._handlersByName[name];
|
||||||
if (handlers) {
|
if (handlers) {
|
||||||
handlers.delete(callback);
|
handlers.delete(callback);
|
||||||
if (handlers.length === 0) {
|
if (handlers.length === 0) {
|
||||||
delete this._handlersByName[name];
|
delete this._handlersByName[name];
|
||||||
this.onLastSubscriptionRemoved(name);
|
this.onLastSubscriptionRemoved(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFirstSubscriptionAdded(name) {}
|
onFirstSubscriptionAdded(name) {}
|
||||||
|
|
||||||
|
@ -38,38 +38,38 @@ export default class EventEmitter {
|
||||||
}
|
}
|
||||||
//#ifdef TESTS
|
//#ifdef TESTS
|
||||||
export function tests() {
|
export function tests() {
|
||||||
return {
|
return {
|
||||||
test_on_off(assert) {
|
test_on_off(assert) {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const e = new EventEmitter();
|
const e = new EventEmitter();
|
||||||
const callback = () => counter += 1;
|
const callback = () => counter += 1;
|
||||||
e.on("change", callback);
|
e.on("change", callback);
|
||||||
e.emit("change");
|
e.emit("change");
|
||||||
e.off("change", callback);
|
e.off("change", callback);
|
||||||
e.emit("change");
|
e.emit("change");
|
||||||
assert.equal(counter, 1);
|
assert.equal(counter, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
test_emit_value(assert) {
|
test_emit_value(assert) {
|
||||||
let value = 0;
|
let value = 0;
|
||||||
const e = new EventEmitter();
|
const e = new EventEmitter();
|
||||||
const callback = (v) => value = v;
|
const callback = (v) => value = v;
|
||||||
e.on("change", callback);
|
e.on("change", callback);
|
||||||
e.emit("change", 5);
|
e.emit("change", 5);
|
||||||
e.off("change", callback);
|
e.off("change", callback);
|
||||||
assert.equal(value, 5);
|
assert.equal(value, 5);
|
||||||
},
|
},
|
||||||
|
|
||||||
test_double_on(assert) {
|
test_double_on(assert) {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const e = new EventEmitter();
|
const e = new EventEmitter();
|
||||||
const callback = () => counter += 1;
|
const callback = () => counter += 1;
|
||||||
e.on("change", callback);
|
e.on("change", callback);
|
||||||
e.on("change", callback);
|
e.on("change", callback);
|
||||||
e.emit("change");
|
e.emit("change");
|
||||||
e.off("change", callback);
|
e.off("change", callback);
|
||||||
assert.equal(counter, 1);
|
assert.equal(counter, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//#endif
|
//#endif
|
||||||
|
|
40
src/main.js
40
src/main.js
|
@ -12,14 +12,14 @@ const USER_ID = `@${USERNAME}:localhost`;
|
||||||
const PASSWORD = "testtest";
|
const PASSWORD = "testtest";
|
||||||
|
|
||||||
function getSessionInfo(userId) {
|
function getSessionInfo(userId) {
|
||||||
const sessionsJson = localStorage.getItem("brawl_sessions_v1");
|
const sessionsJson = localStorage.getItem("brawl_sessions_v1");
|
||||||
if (sessionsJson) {
|
if (sessionsJson) {
|
||||||
const sessions = JSON.parse(sessionsJson);
|
const sessions = JSON.parse(sessionsJson);
|
||||||
const session = sessions.find(session => session.userId === userId);
|
const session = sessions.find(session => session.userId === userId);
|
||||||
if (session) {
|
if (session) {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeSessionInfo(loginData) {
|
function storeSessionInfo(loginData) {
|
||||||
|
@ -39,9 +39,9 @@ function storeSessionInfo(loginData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(username, password, homeserver) {
|
async function login(username, password, homeserver) {
|
||||||
const hsApi = new HomeServerApi(homeserver);
|
const hsApi = new HomeServerApi(homeserver);
|
||||||
const loginData = await hsApi.passwordLogin(username, password).response();
|
const loginData = await hsApi.passwordLogin(username, password).response();
|
||||||
return storeSessionInfo(loginData);
|
return storeSessionInfo(loginData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSession(container, session, sync) {
|
function showSession(container, session, sync) {
|
||||||
|
@ -51,11 +51,11 @@ function showSession(container, session, sync) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function main(container) {
|
export default async function main(container) {
|
||||||
try {
|
try {
|
||||||
let sessionInfo = getSessionInfo(USER_ID);
|
let sessionInfo = getSessionInfo(USER_ID);
|
||||||
if (!sessionInfo) {
|
if (!sessionInfo) {
|
||||||
sessionInfo = await login(USERNAME, PASSWORD, HOMESERVER);
|
sessionInfo = await login(USERNAME, PASSWORD, HOMESERVER);
|
||||||
}
|
}
|
||||||
const storage = await createIdbStorage(`brawl_session_${sessionInfo.id}`);
|
const storage = await createIdbStorage(`brawl_session_${sessionInfo.id}`);
|
||||||
const hsApi = new HomeServerApi(HOMESERVER, sessionInfo.accessToken);
|
const hsApi = new HomeServerApi(HOMESERVER, sessionInfo.accessToken);
|
||||||
const session = new Session({storage, hsApi, sessionInfo: {
|
const session = new Session({storage, hsApi, sessionInfo: {
|
||||||
|
@ -65,18 +65,18 @@ export default async function main(container) {
|
||||||
}});
|
}});
|
||||||
await session.load();
|
await session.load();
|
||||||
console.log("session loaded");
|
console.log("session loaded");
|
||||||
const sync = new Sync(hsApi, session, storage);
|
const sync = new Sync(hsApi, session, storage);
|
||||||
const needsInitialSync = !session.syncToken;
|
const needsInitialSync = !session.syncToken;
|
||||||
if (needsInitialSync) {
|
if (needsInitialSync) {
|
||||||
console.log("session needs initial sync");
|
console.log("session needs initial sync");
|
||||||
} else {
|
} else {
|
||||||
showSession(container, session, sync);
|
showSession(container, session, sync);
|
||||||
}
|
}
|
||||||
await sync.start();
|
await sync.start();
|
||||||
if (needsInitialSync) {
|
if (needsInitialSync) {
|
||||||
showSession(container, session, sync);
|
showSession(container, session, sync);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error(`${err.message}:\n${err.stack}`);
|
console.error(`${err.message}:\n${err.stack}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export class HomeServerError extends Error {
|
export class HomeServerError extends Error {
|
||||||
constructor(method, url, body) {
|
constructor(method, url, body) {
|
||||||
super(`${body.error} on ${method} ${url}`);
|
super(`${body.error} on ${method} ${url}`);
|
||||||
this.errcode = body.errcode;
|
this.errcode = body.errcode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RequestAbortError extends Error {
|
export class RequestAbortError extends Error {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import {
|
import {
|
||||||
HomeServerError,
|
HomeServerError,
|
||||||
RequestAbortError,
|
RequestAbortError,
|
||||||
NetworkError
|
NetworkError
|
||||||
} from "./error.js";
|
} from "./error.js";
|
||||||
|
|
||||||
class RequestWrapper {
|
class RequestWrapper {
|
||||||
constructor(promise, controller) {
|
constructor(promise, controller) {
|
||||||
if (!controller) {
|
if (!controller) {
|
||||||
const abortPromise = new Promise((_, reject) => {
|
const abortPromise = new Promise((_, reject) => {
|
||||||
this._controller = {
|
this._controller = {
|
||||||
|
@ -21,63 +21,63 @@ class RequestWrapper {
|
||||||
this._promise = promise;
|
this._promise = promise;
|
||||||
this._controller = controller;
|
this._controller = controller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
this._controller.abort();
|
this._controller.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
response() {
|
response() {
|
||||||
return this._promise;
|
return this._promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class HomeServerApi {
|
export default class HomeServerApi {
|
||||||
constructor(homeserver, accessToken) {
|
constructor(homeserver, accessToken) {
|
||||||
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
||||||
// one could change the homeserver as well so the token gets sent there, so both must be protected from read/write
|
// one could change the homeserver as well so the token gets sent there, so both must be protected from read/write
|
||||||
this._homeserver = homeserver;
|
this._homeserver = homeserver;
|
||||||
this._accessToken = accessToken;
|
this._accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
_url(csPath) {
|
_url(csPath) {
|
||||||
return `${this._homeserver}/_matrix/client/r0${csPath}`;
|
return `${this._homeserver}/_matrix/client/r0${csPath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_request(method, csPath, queryParams = {}, body) {
|
_request(method, csPath, queryParams = {}, body) {
|
||||||
const queryString = Object.entries(queryParams)
|
const queryString = Object.entries(queryParams)
|
||||||
.filter(([, value]) => value !== undefined)
|
.filter(([, value]) => value !== undefined)
|
||||||
.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
|
.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
|
||||||
.join("&");
|
.join("&");
|
||||||
const url = this._url(`${csPath}?${queryString}`);
|
const url = this._url(`${csPath}?${queryString}`);
|
||||||
let bodyString;
|
let bodyString;
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
if (this._accessToken) {
|
if (this._accessToken) {
|
||||||
headers.append("Authorization", `Bearer ${this._accessToken}`);
|
headers.append("Authorization", `Bearer ${this._accessToken}`);
|
||||||
}
|
}
|
||||||
headers.append("Accept", "application/json");
|
headers.append("Accept", "application/json");
|
||||||
if (body) {
|
if (body) {
|
||||||
headers.append("Content-Type", "application/json");
|
headers.append("Content-Type", "application/json");
|
||||||
bodyString = JSON.stringify(body);
|
bodyString = JSON.stringify(body);
|
||||||
}
|
}
|
||||||
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
||||||
// TODO: set authenticated headers with second arguments, cache them
|
// TODO: set authenticated headers with second arguments, cache them
|
||||||
let promise = fetch(url, {
|
let promise = fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
body: bodyString,
|
body: bodyString,
|
||||||
signal: controller && controller.signal
|
signal: controller && controller.signal
|
||||||
});
|
});
|
||||||
promise = promise.then(async (response) => {
|
promise = promise.then(async (response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} else {
|
} else {
|
||||||
switch (response.status) {
|
switch (response.status) {
|
||||||
default:
|
default:
|
||||||
throw new HomeServerError(method, url, await response.json())
|
throw new HomeServerError(method, url, await response.json())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
if (err.name === "AbortError") {
|
if (err.name === "AbortError") {
|
||||||
throw new RequestAbortError();
|
throw new RequestAbortError();
|
||||||
} else if (err instanceof TypeError) {
|
} else if (err instanceof TypeError) {
|
||||||
|
@ -89,29 +89,29 @@ export default class HomeServerApi {
|
||||||
// but the 2 later ones are indistinguishable from javascript.
|
// but the 2 later ones are indistinguishable from javascript.
|
||||||
throw new NetworkError(err.message);
|
throw new NetworkError(err.message);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
return new RequestWrapper(promise, controller);
|
return new RequestWrapper(promise, controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
_post(csPath, queryParams, body) {
|
_post(csPath, queryParams, body) {
|
||||||
return this._request("POST", csPath, queryParams, body);
|
return this._request("POST", csPath, queryParams, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
_get(csPath, queryParams, body) {
|
_get(csPath, queryParams, body) {
|
||||||
return this._request("GET", csPath, queryParams, body);
|
return this._request("GET", csPath, queryParams, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
sync(since, filter, timeout) {
|
sync(since, filter, timeout) {
|
||||||
return this._get("/sync", {since, timeout, filter});
|
return this._get("/sync", {since, timeout, filter});
|
||||||
}
|
}
|
||||||
|
|
||||||
// params is from, dir and optionally to, limit, filter.
|
// params is from, dir and optionally to, limit, filter.
|
||||||
messages(roomId, params) {
|
messages(roomId, params) {
|
||||||
return this._get(`/rooms/${roomId}/messages`, params);
|
return this._get(`/rooms/${roomId}/messages`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordLogin(username, password) {
|
passwordLogin(username, password) {
|
||||||
return this._post("/login", undefined, {
|
return this._post("/login", undefined, {
|
||||||
"type": "m.login.password",
|
"type": "m.login.password",
|
||||||
"identifier": {
|
"identifier": {
|
||||||
|
@ -120,5 +120,5 @@ export default class HomeServerApi {
|
||||||
},
|
},
|
||||||
"password": password
|
"password": password
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {iterateCursor, reqAsPromise} from "./utils.js";
|
import {iterateCursor, reqAsPromise} from "./utils.js";
|
||||||
|
|
||||||
export default class QueryTarget {
|
export default class QueryTarget {
|
||||||
constructor(target) {
|
constructor(target) {
|
||||||
this._target = target;
|
this._target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
_openCursor(range, direction) {
|
_openCursor(range, direction) {
|
||||||
if (range && direction) {
|
if (range && direction) {
|
||||||
|
@ -21,55 +21,55 @@ export default class QueryTarget {
|
||||||
return reqAsPromise(this._target.get(key));
|
return reqAsPromise(this._target.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
reduce(range, reducer, initialValue) {
|
reduce(range, reducer, initialValue) {
|
||||||
return this._reduce(range, reducer, initialValue, "next");
|
return this._reduce(range, reducer, initialValue, "next");
|
||||||
}
|
}
|
||||||
|
|
||||||
reduceReverse(range, reducer, initialValue) {
|
reduceReverse(range, reducer, initialValue) {
|
||||||
return this._reduce(range, reducer, initialValue, "prev");
|
return this._reduce(range, reducer, initialValue, "prev");
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLimit(range, amount) {
|
selectLimit(range, amount) {
|
||||||
return this._selectLimit(range, amount, "next");
|
return this._selectLimit(range, amount, "next");
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLimitReverse(range, amount) {
|
selectLimitReverse(range, amount) {
|
||||||
return this._selectLimit(range, amount, "prev");
|
return this._selectLimit(range, amount, "prev");
|
||||||
}
|
}
|
||||||
|
|
||||||
selectWhile(range, predicate) {
|
selectWhile(range, predicate) {
|
||||||
return this._selectWhile(range, predicate, "next");
|
return this._selectWhile(range, predicate, "next");
|
||||||
}
|
}
|
||||||
|
|
||||||
selectWhileReverse(range, predicate) {
|
selectWhileReverse(range, predicate) {
|
||||||
return this._selectWhile(range, predicate, "prev");
|
return this._selectWhile(range, predicate, "prev");
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectAll(range, direction) {
|
async selectAll(range, direction) {
|
||||||
const cursor = this._openCursor(range, direction);
|
const cursor = this._openCursor(range, direction);
|
||||||
const results = [];
|
const results = [];
|
||||||
await iterateCursor(cursor, (value) => {
|
await iterateCursor(cursor, (value) => {
|
||||||
results.push(value);
|
results.push(value);
|
||||||
return {done: false};
|
return {done: false};
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectFirst(range) {
|
selectFirst(range) {
|
||||||
return this._find(range, () => true, "next");
|
return this._find(range, () => true, "next");
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLast(range) {
|
selectLast(range) {
|
||||||
return this._find(range, () => true, "prev");
|
return this._find(range, () => true, "prev");
|
||||||
}
|
}
|
||||||
|
|
||||||
find(range, predicate) {
|
find(range, predicate) {
|
||||||
return this._find(range, predicate, "next");
|
return this._find(range, predicate, "next");
|
||||||
}
|
}
|
||||||
|
|
||||||
findReverse(range, predicate) {
|
findReverse(range, predicate) {
|
||||||
return this._find(range, predicate, "prev");
|
return this._find(range, predicate, "prev");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given set of keys exist.
|
* Checks if a given set of keys exist.
|
||||||
|
@ -107,43 +107,43 @@ export default class QueryTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_reduce(range, reducer, initialValue, direction) {
|
_reduce(range, reducer, initialValue, direction) {
|
||||||
let reducedValue = initialValue;
|
let reducedValue = initialValue;
|
||||||
const cursor = this._openCursor(range, direction);
|
const cursor = this._openCursor(range, direction);
|
||||||
return iterateCursor(cursor, (value) => {
|
return iterateCursor(cursor, (value) => {
|
||||||
reducedValue = reducer(reducedValue, value);
|
reducedValue = reducer(reducedValue, value);
|
||||||
return {done: false};
|
return {done: false};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectLimit(range, amount, direction) {
|
_selectLimit(range, amount, direction) {
|
||||||
return this._selectWhile(range, (results) => {
|
return this._selectWhile(range, (results) => {
|
||||||
return results.length === amount;
|
return results.length === amount;
|
||||||
}, direction);
|
}, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _selectWhile(range, predicate, direction) {
|
async _selectWhile(range, predicate, direction) {
|
||||||
const cursor = this._openCursor(range, direction);
|
const cursor = this._openCursor(range, direction);
|
||||||
const results = [];
|
const results = [];
|
||||||
await iterateCursor(cursor, (value) => {
|
await iterateCursor(cursor, (value) => {
|
||||||
results.push(value);
|
results.push(value);
|
||||||
return {done: predicate(results)};
|
return {done: predicate(results)};
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _find(range, predicate, direction) {
|
async _find(range, predicate, direction) {
|
||||||
const cursor = this._openCursor(range, direction);
|
const cursor = this._openCursor(range, direction);
|
||||||
let result;
|
let result;
|
||||||
const found = await iterateCursor(cursor, (value) => {
|
const found = await iterateCursor(cursor, (value) => {
|
||||||
const found = predicate(value);
|
const found = predicate(value);
|
||||||
if (found) {
|
if (found) {
|
||||||
result = value;
|
result = value;
|
||||||
}
|
}
|
||||||
return {done: found};
|
return {done: found};
|
||||||
});
|
});
|
||||||
if (found) {
|
if (found) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,39 +2,39 @@ import Transaction from "./transaction.js";
|
||||||
import { STORE_NAMES, StorageError } from "../common.js";
|
import { STORE_NAMES, StorageError } from "../common.js";
|
||||||
|
|
||||||
export default class Storage {
|
export default class Storage {
|
||||||
constructor(idbDatabase) {
|
constructor(idbDatabase) {
|
||||||
this._db = idbDatabase;
|
this._db = idbDatabase;
|
||||||
const nameMap = STORE_NAMES.reduce((nameMap, name) => {
|
const nameMap = STORE_NAMES.reduce((nameMap, name) => {
|
||||||
nameMap[name] = name;
|
nameMap[name] = name;
|
||||||
return nameMap;
|
return nameMap;
|
||||||
}, {});
|
}, {});
|
||||||
this.storeNames = Object.freeze(nameMap);
|
this.storeNames = Object.freeze(nameMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
_validateStoreNames(storeNames) {
|
_validateStoreNames(storeNames) {
|
||||||
const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name));
|
const idx = storeNames.findIndex(name => !STORE_NAMES.includes(name));
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`);
|
throw new StorageError(`Tried top, a transaction unknown store ${storeNames[idx]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readTxn(storeNames) {
|
async readTxn(storeNames) {
|
||||||
this._validateStoreNames(storeNames);
|
this._validateStoreNames(storeNames);
|
||||||
try {
|
try {
|
||||||
const txn = this._db.transaction(storeNames, "readonly");
|
const txn = this._db.transaction(storeNames, "readonly");
|
||||||
return new Transaction(txn, storeNames);
|
return new Transaction(txn, storeNames);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new StorageError("readTxn failed", err);
|
throw new StorageError("readTxn failed", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readWriteTxn(storeNames) {
|
async readWriteTxn(storeNames) {
|
||||||
this._validateStoreNames(storeNames);
|
this._validateStoreNames(storeNames);
|
||||||
try {
|
try {
|
||||||
const txn = this._db.transaction(storeNames, "readwrite");
|
const txn = this._db.transaction(storeNames, "readwrite");
|
||||||
return new Transaction(txn, storeNames);
|
return new Transaction(txn, storeNames);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new StorageError("readWriteTxn failed", err);
|
throw new StorageError("readWriteTxn failed", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,23 +57,23 @@ class QueryTargetWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Store extends QueryTarget {
|
export default class Store extends QueryTarget {
|
||||||
constructor(idbStore) {
|
constructor(idbStore) {
|
||||||
super(new QueryTargetWrapper(idbStore));
|
super(new QueryTargetWrapper(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
get _idbStore() {
|
get _idbStore() {
|
||||||
return this._target;
|
return this._target;
|
||||||
}
|
}
|
||||||
|
|
||||||
index(indexName) {
|
index(indexName) {
|
||||||
return new QueryTarget(new QueryTargetWrapper(this._idbStore.index(indexName)));
|
return new QueryTarget(new QueryTargetWrapper(this._idbStore.index(indexName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
put(value) {
|
put(value) {
|
||||||
return reqAsPromise(this._idbStore.put(value));
|
return reqAsPromise(this._idbStore.put(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
add(value) {
|
add(value) {
|
||||||
return reqAsPromise(this._idbStore.add(value));
|
return reqAsPromise(this._idbStore.add(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,9 +87,9 @@ class Range {
|
||||||
* @property {?Gap} gap if a gap entry, the gap
|
* @property {?Gap} gap if a gap entry, the gap
|
||||||
*/
|
*/
|
||||||
export default class TimelineEventStore {
|
export default class TimelineEventStore {
|
||||||
constructor(timelineStore) {
|
constructor(timelineStore) {
|
||||||
this._timelineStore = timelineStore;
|
this._timelineStore = timelineStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a range that only includes the given key
|
/** Creates a range that only includes the given key
|
||||||
* @param {EventKey} eventKey the key
|
* @param {EventKey} eventKey the key
|
||||||
|
@ -134,11 +134,11 @@ export default class TimelineEventStore {
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
async lastEvents(roomId, fragmentId, amount) {
|
async lastEvents(roomId, fragmentId, amount) {
|
||||||
const eventKey = EventKey.maxKey;
|
const eventKey = EventKey.maxKey;
|
||||||
eventKey.fragmentId = fragmentId;
|
eventKey.fragmentId = fragmentId;
|
||||||
return this.eventsBefore(roomId, eventKey, amount);
|
return this.eventsBefore(roomId, eventKey, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up the first `amount` entries in the timeline for `roomId`.
|
/** Looks up the first `amount` entries in the timeline for `roomId`.
|
||||||
* @param {string} roomId
|
* @param {string} roomId
|
||||||
|
@ -146,11 +146,11 @@ export default class TimelineEventStore {
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
async firstEvents(roomId, fragmentId, amount) {
|
async firstEvents(roomId, fragmentId, amount) {
|
||||||
const eventKey = EventKey.minKey;
|
const eventKey = EventKey.minKey;
|
||||||
eventKey.fragmentId = fragmentId;
|
eventKey.fragmentId = fragmentId;
|
||||||
return this.eventsAfter(roomId, eventKey, amount);
|
return this.eventsAfter(roomId, eventKey, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up `amount` entries after `eventKey` in the timeline for `roomId` within the same fragment.
|
/** Looks up `amount` entries after `eventKey` in the timeline for `roomId` within the same fragment.
|
||||||
* The entry for `eventKey` is not included.
|
* The entry for `eventKey` is not included.
|
||||||
|
@ -159,10 +159,10 @@ export default class TimelineEventStore {
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
* @return {Promise<Entry[]>} a promise resolving to an array with 0 or more entries, in ascending order.
|
||||||
*/
|
*/
|
||||||
eventsAfter(roomId, eventKey, amount) {
|
eventsAfter(roomId, eventKey, amount) {
|
||||||
const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
const idbRange = this.lowerBoundRange(eventKey, true).asIDBKeyRange(roomId);
|
||||||
return this._timelineStore.selectLimit(idbRange, amount);
|
return this._timelineStore.selectLimit(idbRange, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Looks up `amount` entries before `eventKey` in the timeline for `roomId` within the same fragment.
|
/** Looks up `amount` entries before `eventKey` in the timeline for `roomId` within the same fragment.
|
||||||
* The entry for `eventKey` is not included.
|
* The entry for `eventKey` is not included.
|
||||||
|
|
|
@ -8,58 +8,58 @@ import RoomStateStore from "./stores/RoomStateStore.js";
|
||||||
import TimelineFragmentStore from "./stores/TimelineFragmentStore.js";
|
import TimelineFragmentStore from "./stores/TimelineFragmentStore.js";
|
||||||
|
|
||||||
export default class Transaction {
|
export default class Transaction {
|
||||||
constructor(txn, allowedStoreNames) {
|
constructor(txn, allowedStoreNames) {
|
||||||
this._txn = txn;
|
this._txn = txn;
|
||||||
this._allowedStoreNames = allowedStoreNames;
|
this._allowedStoreNames = allowedStoreNames;
|
||||||
this._stores = {
|
this._stores = {
|
||||||
session: null,
|
session: null,
|
||||||
roomSummary: null,
|
roomSummary: null,
|
||||||
roomTimeline: null,
|
roomTimeline: null,
|
||||||
roomState: null,
|
roomState: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_idbStore(name) {
|
_idbStore(name) {
|
||||||
if (!this._allowedStoreNames.includes(name)) {
|
if (!this._allowedStoreNames.includes(name)) {
|
||||||
// more specific error? this is a bug, so maybe not ...
|
// more specific error? this is a bug, so maybe not ...
|
||||||
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
|
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
|
||||||
}
|
}
|
||||||
return new Store(this._txn.objectStore(name));
|
return new Store(this._txn.objectStore(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
_store(name, mapStore) {
|
_store(name, mapStore) {
|
||||||
if (!this._stores[name]) {
|
if (!this._stores[name]) {
|
||||||
const idbStore = this._idbStore(name);
|
const idbStore = this._idbStore(name);
|
||||||
this._stores[name] = mapStore(idbStore);
|
this._stores[name] = mapStore(idbStore);
|
||||||
}
|
}
|
||||||
return this._stores[name];
|
return this._stores[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
get session() {
|
get session() {
|
||||||
return this._store("session", idbStore => new SessionStore(idbStore));
|
return this._store("session", idbStore => new SessionStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
get roomSummary() {
|
get roomSummary() {
|
||||||
return this._store("roomSummary", idbStore => new RoomSummaryStore(idbStore));
|
return this._store("roomSummary", idbStore => new RoomSummaryStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
get timelineFragments() {
|
get timelineFragments() {
|
||||||
return this._store("timelineFragments", idbStore => new TimelineFragmentStore(idbStore));
|
return this._store("timelineFragments", idbStore => new TimelineFragmentStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
get timelineEvents() {
|
get timelineEvents() {
|
||||||
return this._store("timelineEvents", idbStore => new TimelineEventStore(idbStore));
|
return this._store("timelineEvents", idbStore => new TimelineEventStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
get roomState() {
|
get roomState() {
|
||||||
return this._store("roomState", idbStore => new RoomStateStore(idbStore));
|
return this._store("roomState", idbStore => new RoomStateStore(idbStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
complete() {
|
complete() {
|
||||||
return txnAsPromise(this._txn);
|
return txnAsPromise(this._txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
this._txn.abort();
|
this._txn.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
export default {
|
export default {
|
||||||
get minStorageKey() {
|
get minStorageKey() {
|
||||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
get middleStorageKey() {
|
get middleStorageKey() {
|
||||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||||
return 0x7FFFFFFF;
|
return 0x7FFFFFFF;
|
||||||
},
|
},
|
||||||
|
|
||||||
get maxStorageKey() {
|
get maxStorageKey() {
|
||||||
// for indexeddb, we use unsigned 32 bit integers as keys
|
// for indexeddb, we use unsigned 32 bit integers as keys
|
||||||
return 0xFFFFFFFF;
|
return 0xFFFFFFFF;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue