forked from mystiq/hydrogen-web
Merge branch 'master' into bwindels/memberlist
This commit is contained in:
commit
9ff4f3839c
195 changed files with 8062 additions and 609 deletions
|
@ -5,10 +5,10 @@ module.exports = {
|
|||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
177
LICENSE
Normal file
177
LICENSE
Normal file
|
@ -0,0 +1,177 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
23
README.md
23
README.md
|
@ -1,22 +1,25 @@
|
|||
# Brawl
|
||||
# Hydrogen
|
||||
|
||||
A minimal [Matrix](https://matrix.org/) chat client, focused on performance, offline functionality and working on my Lumia 950 Windows Phone.
|
||||
A minimal [Matrix](https://matrix.org/) chat client, focused on performance, offline functionality, and broad browser support. This is work in progress and not yet ready for primetime. We're currently not accepting any externally reported issues (features, bug reports, ...) at this time.
|
||||
|
||||
## Goals
|
||||
|
||||
Hydrogen's goals are:
|
||||
- Work well on desktop as well as mobile browsers
|
||||
- UI components can be easily used in isolation
|
||||
- It is a standalone webapp, but can also be easily embedded into an existing website/webapp to add chat capabilities.
|
||||
- Loading (unused) parts of the application after initial page load should be supported
|
||||
|
||||
## Status
|
||||
|
||||
Brawl can currently log you in, or pick an existing session, sync already joined rooms, fill gaps in the timeline, and send text messages. Everything is stored locally.
|
||||
|
||||
Here's an (outdated) GIF of what that looks like, also see link below to try it out:
|
||||
![Showing multiple sessions, and sending messages](https://bwindels.github.io/brawl-chat/images/brawl-sending.gif)
|
||||
Hydrogen can currently log you in, or pick an existing session, sync already joined rooms, fill gaps in the timeline, and send text messages. Everything is stored locally.
|
||||
|
||||
## Why
|
||||
|
||||
I started writing Brawl both to have a functional matrix client on my aging phone, and to play around with some ideas I had how to use indexeddb optimally in a matrix client.
|
||||
|
||||
For every interaction or network response (syncing, filling a gap), Brawl starts a transaction in indexedb, and only commits it once everything went well. This helps to keep your storage always in a consistent state. As little data is kept in memory as well, and while scrolling in the above GIF, everything is loaded straight from the storage.
|
||||
For every interaction or network response (syncing, filling a gap), Hydrogen starts a transaction in indexedb, and only commits it once everything went well. This helps to keep your storage always in a consistent state. As little data is kept in memory as well, and while scrolling in the above GIF, everything is loaded straight from the storage.
|
||||
|
||||
If you find this interesting, feel free to reach me at `@bwindels:matrix.org`.
|
||||
|
||||
# How to use
|
||||
|
||||
You can [try Brawl here](https://bwindels.github.io/brawl/), or try it locally by running `yarn install` (only the first time) and `yarn start` in the terminal, and point your browser to `http://localhost:3000`.
|
||||
Try it locally by running `npm install dev` (only the first time) and `npm start` in the terminal, and point your browser to `http://localhost:3000`.
|
||||
|
|
13
TODO.md
Normal file
13
TODO.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
- make it a copy, not a fork of brawl, so we can have issues
|
||||
- add compilation step for ie11 compatible bundle
|
||||
- compile to es5
|
||||
- use bluebird for promises
|
||||
- make xhr request impl
|
||||
- once app is loading, go over errors
|
||||
|
||||
|
||||
- project goals
|
||||
- works on mobile
|
||||
- works well offline
|
||||
- components can be used in isolation
|
||||
- lazyload components?
|
|
@ -16,3 +16,7 @@ As a UI for reactions, we could show (👍 14 + 1) where the + 1 is our own loca
|
|||
|
||||
|
||||
wrt to how to store relations in indexeddb, we could store all local ids of related events (per type?) on the related-to event, so we can fetch them in one query for *all* events that have related events that were fetched in a range, without needing another index that would slow down writes. So that would only add 1 query which we only need to do when there are relations in the TimelineReader. what do we do though if we receive the relating event before the related-to event? An index would fix this mostly ... or we need a temp store where we store unresolved relations...
|
||||
|
||||
Replies should definitely use this relation mechanism, so we can easily show the most up to date version of the replied-to event.
|
||||
|
||||
Redactions can de done separately
|
||||
|
|
37
index.html
37
index.html
|
@ -3,49 +3,28 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<meta name="application-name" content="Brawl Chat"/>
|
||||
<meta name="application-name" content="Hydrogen Chat"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="Brawl Chat">
|
||||
<meta name="apple-mobile-web-app-title" content="Hydrogen Chat">
|
||||
<meta name="description" content="A matrix chat application">
|
||||
<link rel="stylesheet" type="text/css" href="src/ui/web/css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="src/ui/web/css/themes/element/theme.css" title="Element Theme">
|
||||
<link rel="alternate stylesheet" type="text/css" href="src/ui/web/css/themes/bubbles/theme.css" title="Bubbles Theme">
|
||||
</head>
|
||||
<body class="brawl">
|
||||
<body class="hydrogen">
|
||||
<script id="version" type="disabled">
|
||||
window.BRAWL_VERSION = "%%VERSION%%";
|
||||
</script>
|
||||
<script id="phone-debug-pre" type="disabled">
|
||||
window.DEBUG = true;
|
||||
window.debugConsoleBuffer = "";
|
||||
console.error = (...params) => {
|
||||
const lastLines = "...\n" + window.debugConsoleBuffer.split("\n").slice(-10).join("\n");
|
||||
// window.debugConsoleBuffer = window.debugConsoleBuffer + "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) => {
|
||||
window.debugConsoleBuffer = window.debugConsoleBuffer + params.join(" ") + "\n";
|
||||
};
|
||||
window.HYDROGEN_VERSION = "%%VERSION%%";
|
||||
</script>
|
||||
<script id="main" type="module">
|
||||
import main from "./src/main.js";
|
||||
import {main} from "./src/main.js";
|
||||
main(document.body);
|
||||
</script>
|
||||
<script id="phone-debug-post" type="disabled">
|
||||
setTimeout(() => {
|
||||
const showlogs = document.getElementById("showlogs");
|
||||
showlogs.addEventListener("click", () => {
|
||||
const lastLines = "...\n" + window.debugConsoleBuffer.split("\n").slice(-20).join("\n");
|
||||
alert(lastLines);
|
||||
}, true);
|
||||
showlogs.innerText = "Show last 20 log lines";
|
||||
}, 5000);
|
||||
</script>
|
||||
<script id="service-worker" type="disabled">
|
||||
if('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
.then(function() { console.log("Service Worker registered"); });
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
2313
package-lock.json
generated
Normal file
2313
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
38
package.json
38
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "brawl-chat",
|
||||
"version": "0.0.27",
|
||||
"name": "hydrogen-web",
|
||||
"version": "0.0.29",
|
||||
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
|
@ -13,24 +13,36 @@
|
|||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/bwindels/brawl-chat.git"
|
||||
"url": "git@github.com:vector-im/hydrogen-web.git"
|
||||
},
|
||||
"author": "Bruno Windels",
|
||||
"license": "ISC",
|
||||
"author": "matrix.org",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bwindels/brawl-chat/issues"
|
||||
"url": "https://github.com/vector-im/hydrogen-web/issues"
|
||||
},
|
||||
"homepage": "https://github.com/bwindels/brawl-chat#readme",
|
||||
"homepage": "https://github.com/vector-im/hydrogen-web/#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@rollup/plugin-babel": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^15.0.0",
|
||||
"@rollup/plugin-multi-entry": "^4.0.0",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"commander": "^6.0.0",
|
||||
"core-js": "^3.6.5",
|
||||
"finalhandler": "^1.1.1",
|
||||
"impunity": "^0.0.11",
|
||||
"postcss": "^7.0.18",
|
||||
"mdn-polyfills": "^5.20.0",
|
||||
"postcss": "^7.0.32",
|
||||
"postcss-css-variables": "^0.17.0",
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"rollup": "^1.15.6",
|
||||
"serve-static": "^1.13.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz"
|
||||
"postcss-url": "^8.0.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rollup": "^2.26.4",
|
||||
"rollup-plugin-cleanup": "^3.1.1",
|
||||
"serve-static": "^1.13.2",
|
||||
"xxhash": "^0.3.0"
|
||||
}
|
||||
}
|
||||
|
|
17
prototypes/detect-ie11-css.html
Normal file
17
prototypes/detect-ie11-css.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" type="text/css" href="non-ie11.css" media="not all and (-ms-high-contrast: none), (-ms-high-contrast: active)">
|
||||
<link rel="stylesheet" type="text/css" href="ie11.css" media="all and (-ms-high-contrast: none), (-ms-high-contrast: active)">
|
||||
<style type="text/css">
|
||||
/*
|
||||
can't make this work in non-IE browser for now...
|
||||
*/
|
||||
@import url('non-ie11.css') screen @supports(--foo: green);
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Velit dignissim sodales ut eu sem integer vitae justo eget. Libero justo laoreet sit amet cursus sit amet dictum. Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate. Quis eleifend quam adipiscing vitae proin sagittis nisl. Egestas maecenas pharetra convallis posuere morbi leo. Metus dictum at tempor commodo ullamcorper a lacus. Odio pellentesque diam volutpat commodo sed egestas egestas. Elementum eu facilisis sed odio morbi quis commodo odio aenean. Velit euismod in pellentesque massa placerat duis ultricies lacus sed. Feugiat sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi. Pulvinar etiam non quam lacus suspendisse. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim. Proin gravida hendrerit lectus a. Nibh sed pulvinar proin gravida. Massa placerat duis ultricies lacus. Enim sed faucibus turpis in eu mi bibendum neque egestas. Turpis egestas sed tempus urna et pharetra pharetra.</p>
|
||||
</body>
|
||||
</html>
|
3
prototypes/ie11.css
Normal file
3
prototypes/ie11.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
p {
|
||||
color: red;
|
||||
}
|
3
prototypes/non-ie11.css
Normal file
3
prototypes/non-ie11.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
p {
|
||||
color: green;
|
||||
}
|
|
@ -1,49 +1,162 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 cheerio from "cheerio";
|
||||
import fsRoot from "fs";
|
||||
const fs = fsRoot.promises;
|
||||
import path from "path";
|
||||
import rollup from 'rollup';
|
||||
import XXHash from 'xxhash';
|
||||
import { rollup } from 'rollup';
|
||||
import postcss from "postcss";
|
||||
import postcssImport from "postcss-import";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import commander from "commander";
|
||||
// needed for legacy bundle
|
||||
import babel from '@rollup/plugin-babel';
|
||||
// needed to find the polyfill modules in the main-legacy.js bundle
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
// needed because some of the polyfills are written as commonjs modules
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
// multi-entry plugin so we can add polyfill file to main
|
||||
import multi from '@rollup/plugin-multi-entry';
|
||||
import removeJsComments from 'rollup-plugin-cleanup';
|
||||
// replace urls of asset names with content hashed version
|
||||
import postcssUrl from "postcss-url";
|
||||
|
||||
import cssvariables from "postcss-css-variables";
|
||||
import flexbugsFixes from "postcss-flexbugs-fixes";
|
||||
|
||||
const PROJECT_ID = "hydrogen";
|
||||
const PROJECT_SHORT_NAME = "Hydrogen";
|
||||
const PROJECT_NAME = "Hydrogen Chat";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const projectDir = path.join(__dirname, "../");
|
||||
const targetDir = path.join(projectDir, "target");
|
||||
const cssSrcDir = path.join(projectDir, "src/ui/web/css/");
|
||||
const targetDir = path.join(projectDir, "target/");
|
||||
|
||||
const debug = false;
|
||||
const offline = true;
|
||||
const program = new commander.Command();
|
||||
program
|
||||
.option("--no-offline", "make a build without a service worker or appcache manifest")
|
||||
program.parse(process.argv);
|
||||
const {debug, noOffline} = program;
|
||||
const offline = !noOffline;
|
||||
|
||||
async function build() {
|
||||
// only used for CSS for now, using legacy for all targets for now
|
||||
const legacy = true;
|
||||
// get version number
|
||||
const version = JSON.parse(await fs.readFile(path.join(projectDir, "package.json"), "utf8")).version;
|
||||
// clear target dir
|
||||
await removeDirIfExists(targetDir);
|
||||
await fs.mkdir(targetDir);
|
||||
|
||||
await buildHtml(version);
|
||||
await buildJs();
|
||||
await buildCss();
|
||||
if (offline) {
|
||||
await buildOffline(version);
|
||||
}
|
||||
|
||||
console.log(`built brawl ${version} successfully`);
|
||||
}
|
||||
|
||||
async function buildHtml(version) {
|
||||
// transform html file
|
||||
const devHtml = await fs.readFile(path.join(projectDir, "index.html"), "utf8");
|
||||
const doc = cheerio.load(devHtml);
|
||||
doc("link[rel=stylesheet]").attr("href", "brawl.css");
|
||||
const themes = [];
|
||||
findThemes(doc, themeName => {
|
||||
themes.push(themeName);
|
||||
});
|
||||
// clear target dir
|
||||
await removeDirIfExists(targetDir);
|
||||
await createDirs(targetDir, themes);
|
||||
// also creates the directories where the theme css bundles are placed in,
|
||||
// so do it first
|
||||
const themeAssets = await copyThemeAssets(themes, legacy);
|
||||
const jsBundlePath = await buildJs();
|
||||
const jsLegacyBundlePath = await buildJsLegacy();
|
||||
const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes, themeAssets);
|
||||
const assetPaths = createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets);
|
||||
|
||||
let manifestPath;
|
||||
if (offline) {
|
||||
manifestPath = await buildOffline(version, assetPaths);
|
||||
}
|
||||
await buildHtml(doc, version, assetPaths, manifestPath);
|
||||
|
||||
console.log(`built ${PROJECT_ID} ${version} successfully`);
|
||||
}
|
||||
|
||||
function createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets) {
|
||||
function trim(path) {
|
||||
if (!path.startsWith(targetDir)) {
|
||||
throw new Error("invalid target path: " + targetDir);
|
||||
}
|
||||
return path.substr(targetDir.length);
|
||||
}
|
||||
return {
|
||||
jsBundle: () => trim(jsBundlePath),
|
||||
jsLegacyBundle: () => trim(jsLegacyBundlePath),
|
||||
cssMainBundle: () => trim(cssBundlePaths.main),
|
||||
cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]),
|
||||
cssThemeBundles: () => Object.values(cssBundlePaths.themes).map(a => trim(a)),
|
||||
otherAssets: () => Object.values(themeAssets).map(a => trim(a))
|
||||
};
|
||||
}
|
||||
|
||||
async function findThemes(doc, callback) {
|
||||
doc("link[rel~=stylesheet][title]").each((i, el) => {
|
||||
const theme = doc(el);
|
||||
const href = theme.attr("href");
|
||||
const themesPrefix = "/themes/";
|
||||
const prefixIdx = href.indexOf(themesPrefix);
|
||||
if (prefixIdx !== -1) {
|
||||
const themeNameStart = prefixIdx + themesPrefix.length;
|
||||
const themeNameEnd = href.indexOf("/", themeNameStart);
|
||||
const themeName = href.substr(themeNameStart, themeNameEnd - themeNameStart);
|
||||
callback(themeName, theme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createDirs(targetDir, themes) {
|
||||
await fs.mkdir(targetDir);
|
||||
const themeDir = path.join(targetDir, "themes");
|
||||
await fs.mkdir(themeDir);
|
||||
for (const theme of themes) {
|
||||
await fs.mkdir(path.join(themeDir, theme));
|
||||
}
|
||||
}
|
||||
|
||||
async function copyThemeAssets(themes, legacy) {
|
||||
const assets = {};
|
||||
for (const theme of themes) {
|
||||
const themeDstFolder = path.join(targetDir, `themes/${theme}`);
|
||||
const themeSrcFolder = path.join(cssSrcDir, `themes/${theme}`);
|
||||
const themeAssets = await copyFolder(themeSrcFolder, themeDstFolder, file => {
|
||||
const isUnneededFont = legacy ? file.endsWith(".woff2") : file.endsWith(".woff");
|
||||
return !file.endsWith(".css") && !isUnneededFont;
|
||||
});
|
||||
Object.assign(assets, themeAssets);
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
async function buildHtml(doc, version, assetPaths, manifestPath) {
|
||||
// transform html file
|
||||
// change path to main.css to css bundle
|
||||
doc("link[rel=stylesheet]:not([title])").attr("href", assetPaths.cssMainBundle());
|
||||
// change paths to all theme stylesheets
|
||||
findThemes(doc, (themeName, theme) => {
|
||||
theme.attr("href", assetPaths.cssThemeBundle(themeName));
|
||||
});
|
||||
doc("script#main").replaceWith(
|
||||
`<script type="text/javascript" src="brawl.js"></script>` +
|
||||
`<script type="text/javascript">main(document.body);</script>`);
|
||||
removeOrEnableScript(doc("script#phone-debug-pre"), debug);
|
||||
removeOrEnableScript(doc("script#phone-debug-post"), debug);
|
||||
`<script type="module">import {main} from "./${assetPaths.jsBundle()}"; main(document.body);</script>` +
|
||||
`<script type="text/javascript" nomodule src="${assetPaths.jsLegacyBundle()}"></script>` +
|
||||
`<script type="text/javascript" nomodule>${PROJECT_ID}Bundle.main(document.body);</script>`);
|
||||
removeOrEnableScript(doc("script#service-worker"), offline);
|
||||
|
||||
const versionScript = doc("script#version");
|
||||
|
@ -54,68 +167,149 @@ async function buildHtml(version) {
|
|||
|
||||
if (offline) {
|
||||
doc("html").attr("manifest", "manifest.appcache");
|
||||
doc("head").append(`<link rel="manifest" href="manifest.json">`);
|
||||
doc("head").append(`<link rel="manifest" href="${manifestPath.substr(targetDir.length)}">`);
|
||||
}
|
||||
await fs.writeFile(path.join(targetDir, "index.html"), doc.html(), "utf8");
|
||||
}
|
||||
|
||||
async function buildJs() {
|
||||
// create js bundle
|
||||
const rollupConfig = {
|
||||
const bundle = await rollup({
|
||||
input: 'src/main.js',
|
||||
output: {
|
||||
file: path.join(targetDir, "brawl.js"),
|
||||
format: 'iife',
|
||||
name: 'main'
|
||||
}
|
||||
};
|
||||
const bundle = await rollup.rollup(rollupConfig);
|
||||
await bundle.write(rollupConfig);
|
||||
plugins: [removeJsComments({comments: "none"})]
|
||||
});
|
||||
const {output} = await bundle.generate({
|
||||
format: 'es',
|
||||
name: `${PROJECT_ID}Bundle`
|
||||
});
|
||||
const code = output[0].code;
|
||||
const bundlePath = resource(`${PROJECT_ID}.js`, code);
|
||||
await fs.writeFile(bundlePath, code, "utf8");
|
||||
return bundlePath;
|
||||
}
|
||||
|
||||
async function buildOffline(version) {
|
||||
async function buildJsLegacy() {
|
||||
// compile down to whatever IE 11 needs
|
||||
const babelPlugin = babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "entry",
|
||||
corejs: "3",
|
||||
targets: "IE 11"
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
// create js bundle
|
||||
const rollupConfig = {
|
||||
input: ['src/legacy-polyfill.js', 'src/main.js'],
|
||||
plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
|
||||
};
|
||||
const bundle = await rollup(rollupConfig);
|
||||
const {output} = await bundle.generate({
|
||||
format: 'iife',
|
||||
name: `${PROJECT_ID}Bundle`
|
||||
});
|
||||
const code = output[0].code;
|
||||
const bundlePath = resource(`${PROJECT_ID}-legacy.js`, code);
|
||||
await fs.writeFile(bundlePath, code, "utf8");
|
||||
return bundlePath;
|
||||
}
|
||||
|
||||
async function buildOffline(version, assetPaths) {
|
||||
// write offline availability
|
||||
const offlineFiles = ["brawl.js", "brawl.css", "index.html", "icon-192.png"];
|
||||
const offlineFiles = [
|
||||
assetPaths.cssMainBundle(),
|
||||
"index.html",
|
||||
"icon-192.png",
|
||||
].concat(assetPaths.cssThemeBundles());
|
||||
|
||||
// write appcache manifest
|
||||
const manifestLines = [
|
||||
const appCacheLines = [
|
||||
`CACHE MANIFEST`,
|
||||
`# v${version}`,
|
||||
`NETWORK`,
|
||||
`"*"`,
|
||||
`CACHE`,
|
||||
];
|
||||
manifestLines.push(...offlineFiles);
|
||||
const manifest = manifestLines.join("\n") + "\n";
|
||||
await fs.writeFile(path.join(targetDir, "manifest.appcache"), manifest, "utf8");
|
||||
appCacheLines.push(assetPaths.jsLegacyBundle(), ...offlineFiles);
|
||||
const swOfflineFiles = [assetPaths.jsBundle(), ...offlineFiles];
|
||||
const appCacheManifest = appCacheLines.join("\n") + "\n";
|
||||
await fs.writeFile(path.join(targetDir, "manifest.appcache"), appCacheManifest, "utf8");
|
||||
// write service worker
|
||||
let swSource = await fs.readFile(path.join(projectDir, "src/service-worker.template.js"), "utf8");
|
||||
swSource = swSource.replace(`"%%VERSION%%"`, `"${version}"`);
|
||||
swSource = swSource.replace(`"%%FILES%%"`, JSON.stringify(offlineFiles));
|
||||
swSource = swSource.replace(`"%%OFFLINE_FILES%%"`, JSON.stringify(swOfflineFiles));
|
||||
swSource = swSource.replace(`"%%CACHE_FILES%%"`, JSON.stringify(assetPaths.otherAssets()));
|
||||
await fs.writeFile(path.join(targetDir, "sw.js"), swSource, "utf8");
|
||||
// write web manifest
|
||||
const webManifest = {
|
||||
name: "Brawl Chat",
|
||||
short_name: "Brawl",
|
||||
name:PROJECT_NAME,
|
||||
short_name: PROJECT_SHORT_NAME,
|
||||
display: "fullscreen",
|
||||
start_url: "index.html",
|
||||
icons: [{"src": "icon-192.png", "sizes": "192x192", "type": "image/png"}],
|
||||
};
|
||||
await fs.writeFile(path.join(targetDir, "manifest.json"), JSON.stringify(webManifest), "utf8");
|
||||
const manifestJson = JSON.stringify(webManifest);
|
||||
const manifestPath = resource("manifest.json", manifestJson);
|
||||
await fs.writeFile(manifestPath, manifestJson, "utf8");
|
||||
// copy icon
|
||||
// should this icon have a content hash as well?
|
||||
let icon = await fs.readFile(path.join(projectDir, "icon.png"));
|
||||
await fs.writeFile(path.join(targetDir, "icon-192.png"), icon);
|
||||
return manifestPath;
|
||||
}
|
||||
|
||||
async function buildCss() {
|
||||
// create css bundle
|
||||
const cssMainFile = path.join(projectDir, "src/ui/web/css/main.css");
|
||||
const preCss = await fs.readFile(cssMainFile, "utf8");
|
||||
const cssBundler = postcss([postcssImport]);
|
||||
const postCss = await cssBundler.process(preCss, {from: cssMainFile});
|
||||
await fs.writeFile(path.join(targetDir, "brawl.css"), postCss, "utf8");
|
||||
async function buildCssBundles(buildFn, themes, themeAssets) {
|
||||
const bundleCss = await buildFn(path.join(cssSrcDir, "main.css"));
|
||||
const mainDstPath = resource(`${PROJECT_ID}.css`, bundleCss);
|
||||
await fs.writeFile(mainDstPath, bundleCss, "utf8");
|
||||
const bundlePaths = {main: mainDstPath, themes: {}};
|
||||
for (const theme of themes) {
|
||||
const urlBase = path.join(targetDir, `themes/${theme}/`);
|
||||
const assetUrlMapper = ({absolutePath}) => {
|
||||
const hashedDstPath = themeAssets[absolutePath];
|
||||
if (hashedDstPath && hashedDstPath.startsWith(urlBase)) {
|
||||
return hashedDstPath.substr(urlBase.length);
|
||||
}
|
||||
};
|
||||
const themeCss = await buildFn(path.join(cssSrcDir, `themes/${theme}/theme.css`), assetUrlMapper);
|
||||
const themeDstPath = resource(`themes/${theme}/bundle.css`, themeCss);
|
||||
await fs.writeFile(themeDstPath, themeCss, "utf8");
|
||||
bundlePaths.themes[theme] = themeDstPath;
|
||||
}
|
||||
return bundlePaths;
|
||||
}
|
||||
|
||||
async function buildCss(entryPath, urlMapper = null) {
|
||||
const preCss = await fs.readFile(entryPath, "utf8");
|
||||
const options = [postcssImport];
|
||||
if (urlMapper) {
|
||||
options.push(postcssUrl({url: urlMapper}));
|
||||
}
|
||||
const cssBundler = postcss(options);
|
||||
const result = await cssBundler.process(preCss, {from: entryPath});
|
||||
return result.css;
|
||||
}
|
||||
|
||||
async function buildCssLegacy(entryPath, urlMapper = null) {
|
||||
const preCss = await fs.readFile(entryPath, "utf8");
|
||||
const options = [
|
||||
postcssImport,
|
||||
cssvariables(),
|
||||
flexbugsFixes()
|
||||
];
|
||||
if (urlMapper) {
|
||||
options.push(postcssUrl({url: urlMapper}));
|
||||
}
|
||||
const cssBundler = postcss(options);
|
||||
const result = await cssBundler.process(preCss, {from: entryPath});
|
||||
return result.css;
|
||||
}
|
||||
|
||||
function removeOrEnableScript(scriptNode, enable) {
|
||||
if (enable) {
|
||||
|
@ -127,9 +321,7 @@ function removeOrEnableScript(scriptNode, enable) {
|
|||
|
||||
async function removeDirIfExists(targetDir) {
|
||||
try {
|
||||
const files = await fs.readdir(targetDir);
|
||||
await Promise.all(files.map(filename => fs.unlink(path.join(targetDir, filename))));
|
||||
await fs.rmdir(targetDir);
|
||||
await fs.rmdir(targetDir, {recursive: true});
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
|
@ -137,4 +329,42 @@ async function removeDirIfExists(targetDir) {
|
|||
}
|
||||
}
|
||||
|
||||
async function copyFolder(srcRoot, dstRoot, filter) {
|
||||
const assetPaths = {};
|
||||
const dirEnts = await fs.readdir(srcRoot, {withFileTypes: true});
|
||||
for (const dirEnt of dirEnts) {
|
||||
const dstPath = path.join(dstRoot, dirEnt.name);
|
||||
const srcPath = path.join(srcRoot, dirEnt.name);
|
||||
if (dirEnt.isDirectory()) {
|
||||
await fs.mkdir(dstPath);
|
||||
Object.assign(assetPaths, await copyFolder(srcPath, dstPath, filter));
|
||||
} else if (dirEnt.isFile() && filter(srcPath)) {
|
||||
const content = await fs.readFile(srcPath);
|
||||
const hashedDstPath = resource(dstPath, content);
|
||||
await fs.writeFile(hashedDstPath, content);
|
||||
assetPaths[srcPath] = hashedDstPath;
|
||||
}
|
||||
}
|
||||
return assetPaths;
|
||||
}
|
||||
|
||||
function resource(relPath, content) {
|
||||
let fullPath = relPath;
|
||||
if (!relPath.startsWith("/")) {
|
||||
fullPath = path.join(targetDir, relPath);
|
||||
}
|
||||
const hash = contentHash(Buffer.from(content));
|
||||
const dir = path.dirname(fullPath);
|
||||
const extname = path.extname(fullPath);
|
||||
const basename = path.basename(fullPath, extname);
|
||||
return path.join(dir, `${basename}-${hash}${extname}`);
|
||||
}
|
||||
|
||||
function contentHash(str) {
|
||||
var hasher = new XXHash(0);
|
||||
hasher.update(str);
|
||||
return hasher.digest();
|
||||
}
|
||||
|
||||
|
||||
build().catch(err => console.error(err));
|
||||
|
|
6
scripts/deploy.sh
Executable file
6
scripts/deploy.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
git checkout gh-pages
|
||||
cp -R target/* .
|
||||
git add $(find . -maxdepth 1 -type f)
|
||||
git add themes
|
||||
git commit -m "update hydrogen"
|
||||
git checkout master
|
7
scripts/package.sh
Executable file
7
scripts/package.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
VERSION=$(jq -r ".version" package.json)
|
||||
PACKAGE=hydrogen-web-$VERSION.tar.gz
|
||||
yarn build
|
||||
pushd target
|
||||
tar -czvf ../$PACKAGE ./
|
||||
popd
|
||||
echo $PACKAGE
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
const finalhandler = require('finalhandler')
|
||||
const http = require('http')
|
||||
const serveStatic = require('serve-static')
|
||||
|
@ -23,4 +39,4 @@ const server = http.createServer(function onRequest (req, res) {
|
|||
});
|
||||
|
||||
// Listen
|
||||
server.listen(3000);
|
||||
server.listen(3000);
|
||||
|
|
|
@ -1 +1,17 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export {WebPlatform as Platform} from "./ui/web/WebPlatform.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SessionViewModel} from "./session/SessionViewModel.js";
|
||||
import {LoginViewModel} from "./LoginViewModel.js";
|
||||
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {ViewModel} from "./ViewModel.js";
|
||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {LoadStatus, LoginFailure} from "../matrix/SessionContainer.js";
|
||||
import {SyncStatus} from "../matrix/Sync.js";
|
||||
import {ViewModel} from "./ViewModel.js";
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SortedArray} from "../observable/index.js";
|
||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||
import {ViewModel} from "./ViewModel.js";
|
||||
import {avatarInitials, getIdentifierColorNumber} from "./avatar.js";
|
||||
|
||||
class SessionItemViewModel extends ViewModel {
|
||||
constructor(sessionInfo, pickerVM) {
|
||||
|
@ -96,6 +113,14 @@ class SessionItemViewModel extends ViewModel {
|
|||
this.emitChange("exportDataUrl");
|
||||
}
|
||||
}
|
||||
|
||||
get avatarColorNumber() {
|
||||
return getIdentifierColorNumber(this._sessionInfo.userId);
|
||||
}
|
||||
|
||||
get avatarInitials() {
|
||||
return avatarInitials(this._sessionInfo.userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// ViewModel should just be an eventemitter, not an ObservableValue
|
||||
// as in some cases it would really be more convenient to have multiple events (like telling the timeline to scroll down)
|
||||
// we do need to return a disposable from EventEmitter.on, or at least have a method here to easily track a subscription to an EventEmitter
|
||||
|
@ -6,10 +22,10 @@ import {EventEmitter} from "../utils/EventEmitter.js";
|
|||
import {Disposables} from "../utils/Disposables.js";
|
||||
|
||||
export class ViewModel extends EventEmitter {
|
||||
constructor({clock} = {}) {
|
||||
constructor({clock, emitChange} = {}) {
|
||||
super();
|
||||
this.disposables = null;
|
||||
this._options = {clock};
|
||||
this._options = {clock, emitChange};
|
||||
}
|
||||
|
||||
childOptions(explicitOptions) {
|
||||
|
@ -54,8 +70,16 @@ export class ViewModel extends EventEmitter {
|
|||
return result;
|
||||
}
|
||||
|
||||
updateOptions(options) {
|
||||
this._options = Object.assign(this._options, options);
|
||||
}
|
||||
|
||||
emitChange(changedProps) {
|
||||
this.emit("change", changedProps);
|
||||
if (this._options.emitChange) {
|
||||
this._options.emitChange(changedProps);
|
||||
} else {
|
||||
this.emit("change", changedProps);
|
||||
}
|
||||
}
|
||||
|
||||
get clock() {
|
||||
|
|
49
src/domain/avatar.js
Normal file
49
src/domain/avatar.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function avatarInitials(name) {
|
||||
let firstChar = name.charAt(0);
|
||||
if (firstChar === "!" || firstChar === "@" || firstChar === "#") {
|
||||
firstChar = name.charAt(1);
|
||||
}
|
||||
return firstChar.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates a numeric hash for a given string
|
||||
*
|
||||
* @param {string} str string to hash
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
function hashCode(str) {
|
||||
let hash = 0;
|
||||
let i;
|
||||
let chr;
|
||||
if (str.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
for (i = 0; i < str.length; i++) {
|
||||
chr = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0;
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
export function getIdentifierColorNumber(id) {
|
||||
return (hashCode(id) % 8) + 1;
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {ViewModel} from "../ViewModel.js";
|
||||
import {createEnum} from "../../utils/enum.js";
|
||||
import {ConnectionStatus} from "../../matrix/net/Reconnector.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {RoomTileViewModel} from "./roomlist/RoomTileViewModel.js";
|
||||
import {RoomViewModel} from "./room/RoomViewModel.js";
|
||||
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
|
||||
|
@ -12,12 +28,13 @@ export class SessionViewModel extends ViewModel {
|
|||
sync: sessionContainer.sync,
|
||||
reconnector: sessionContainer.reconnector
|
||||
})));
|
||||
this._currentRoomTileViewModel = null;
|
||||
this._currentRoomViewModel = null;
|
||||
const roomTileVMs = this._session.rooms.mapValues((room, emitUpdate) => {
|
||||
const roomTileVMs = this._session.rooms.mapValues((room, emitChange) => {
|
||||
return new RoomTileViewModel({
|
||||
room,
|
||||
emitUpdate,
|
||||
emitOpen: room => this._openRoom(room)
|
||||
emitChange,
|
||||
emitOpen: this._openRoom.bind(this)
|
||||
});
|
||||
});
|
||||
this._roomList = roomTileVMs.sortValues((a, b) => a.compare(b));
|
||||
|
@ -46,7 +63,11 @@ export class SessionViewModel extends ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
_openRoom(room) {
|
||||
_openRoom(room, roomTileVM) {
|
||||
if (this._currentRoomTileViewModel) {
|
||||
this._currentRoomTileViewModel.close();
|
||||
}
|
||||
this._currentRoomTileViewModel = roomTileVM;
|
||||
if (this._currentRoomViewModel) {
|
||||
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export function avatarInitials(name) {
|
||||
const words = name.split(" ").slice(0, 2);
|
||||
return words.reduce((i, w) => i + w.charAt(0).toUpperCase(), "");
|
||||
}
|
|
@ -1,5 +1,22 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
||||
import {avatarInitials} from "../avatar.js";
|
||||
import {avatarInitials, getIdentifierColorNumber} from "../../avatar.js";
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
|
||||
export class RoomViewModel extends ViewModel {
|
||||
|
@ -74,7 +91,9 @@ export class RoomViewModel extends ViewModel {
|
|||
return avatarInitials(this._room.name);
|
||||
}
|
||||
|
||||
|
||||
get avatarColorNumber() {
|
||||
return getIdentifierColorNumber(this._room.id)
|
||||
}
|
||||
|
||||
async _sendMessage(message) {
|
||||
if (message) {
|
||||
|
@ -97,12 +116,28 @@ export class RoomViewModel extends ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
class ComposerViewModel {
|
||||
class ComposerViewModel extends ViewModel {
|
||||
constructor(roomVM) {
|
||||
super();
|
||||
this._roomVM = roomVM;
|
||||
this._isEmpty = true;
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
return this._roomVM._sendMessage(message);
|
||||
const success = this._roomVM._sendMessage(message);
|
||||
if (success) {
|
||||
this._isEmpty = true;
|
||||
this.emitChange("canSend");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
get canSend() {
|
||||
return !this._isEmpty;
|
||||
}
|
||||
|
||||
setInput(text) {
|
||||
this._isEmpty = text.length === 0;
|
||||
this.emitChange("canSend");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservableList} from "../../../../observable/list/BaseObservableList.js";
|
||||
import {sortedIndex} from "../../../../utils/sortedIndex.js";
|
||||
|
||||
|
@ -185,6 +201,10 @@ export class TilesCollection extends BaseObservableList {
|
|||
get length() {
|
||||
return this._tiles.length;
|
||||
}
|
||||
|
||||
getFirst() {
|
||||
return this._tiles[0];
|
||||
}
|
||||
}
|
||||
|
||||
import {ObservableArray} from "../../../../observable/list/ObservableArray.js";
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
need better naming, but
|
||||
entry = event or gap from matrix layer
|
||||
|
@ -16,19 +33,30 @@ when loading, it just reads events from a sortkey backwards or forwards...
|
|||
*/
|
||||
import {TilesCollection} from "./TilesCollection.js";
|
||||
import {tilesCreator} from "./tilesCreator.js";
|
||||
import {ViewModel} from "../../../ViewModel.js";
|
||||
|
||||
export class TimelineViewModel {
|
||||
constructor({room, timeline, ownUserId}) {
|
||||
export class TimelineViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {room, timeline, ownUserId} = options;
|
||||
this._timeline = timeline;
|
||||
// once we support sending messages we could do
|
||||
// timeline.entries.concat(timeline.pendingEvents)
|
||||
// for an ObservableList that also contains local echos
|
||||
this._tiles = new TilesCollection(timeline.entries, tilesCreator({room, ownUserId}));
|
||||
this._tiles = new TilesCollection(timeline.entries, tilesCreator({room, ownUserId, clock: this.clock}));
|
||||
}
|
||||
|
||||
// doesn't fill gaps, only loads stored entries/tiles
|
||||
loadAtTop() {
|
||||
return this._timeline.loadAtTop(50);
|
||||
/**
|
||||
* @return {bool} startReached if the start of the timeline was reached
|
||||
*/
|
||||
async loadAtTop() {
|
||||
const firstTile = this._tiles.getFirst();
|
||||
if (firstTile.shape === "gap") {
|
||||
return firstTile.fill();
|
||||
} else {
|
||||
await this._timeline.loadAtTop(50);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unloadAtTop(tileAmount) {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class UpdateAction {
|
||||
constructor(remove, update, updateParams) {
|
||||
this._remove = remove;
|
||||
|
|
23
src/domain/session/room/timeline/tiles/EncryptedEventTile.js
Normal file
23
src/domain/session/room/timeline/tiles/EncryptedEventTile.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
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 {MessageTile} from "./MessageTile.js";
|
||||
|
||||
export class EncryptedEventTile extends MessageTile {
|
||||
get text() {
|
||||
return this.i18n`**Encrypted message**`;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SimpleTile} from "./SimpleTile.js";
|
||||
import {UpdateAction} from "../UpdateAction.js";
|
||||
|
||||
|
@ -13,18 +29,20 @@ export class GapTile extends SimpleTile {
|
|||
// prevent doing this twice
|
||||
if (!this._loading) {
|
||||
this._loading = true;
|
||||
this.emitUpdate("isLoading");
|
||||
this.emitChange("isLoading");
|
||||
try {
|
||||
await this._timeline.fillGap(this._entry, 10);
|
||||
} catch (err) {
|
||||
console.error(`timeline.fillGap(): ${err.message}:\n${err.stack}`);
|
||||
this._error = err;
|
||||
this.emitUpdate("error");
|
||||
this.emitChange("error");
|
||||
} finally {
|
||||
this._loading = false;
|
||||
this.emitUpdate("isLoading");
|
||||
this.emitChange("isLoading");
|
||||
}
|
||||
}
|
||||
// edgeReached will have been updated by fillGap
|
||||
return this._entry.edgeReached;
|
||||
}
|
||||
|
||||
updateEntry(entry, params) {
|
||||
|
@ -44,14 +62,6 @@ export class GapTile extends SimpleTile {
|
|||
return this._loading;
|
||||
}
|
||||
|
||||
get isUp() {
|
||||
return this._entry.direction.isBackward;
|
||||
}
|
||||
|
||||
get isDown() {
|
||||
return this._entry.direction.isForward;
|
||||
}
|
||||
|
||||
get error() {
|
||||
if (this._error) {
|
||||
const dir = this._entry.prev_batch ? "previous" : "next";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {MessageTile} from "./MessageTile.js";
|
||||
|
||||
const MAX_HEIGHT = 300;
|
||||
|
@ -10,32 +26,38 @@ export class ImageTile extends MessageTile {
|
|||
}
|
||||
|
||||
get thumbnailUrl() {
|
||||
const mxcUrl = this._getContent().url;
|
||||
return this._room.mxcUrlThumbnail(mxcUrl, this.thumbnailWidth, this.thumbnailHeight, "scale");
|
||||
const mxcUrl = this._getContent()?.url;
|
||||
if (typeof mxcUrl === "string") {
|
||||
return this._room.mxcUrlThumbnail(mxcUrl, this.thumbnailWidth, this.thumbnailHeight, "scale");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get url() {
|
||||
const mxcUrl = this._getContent().url;
|
||||
return this._room.mxcUrl(mxcUrl);
|
||||
const mxcUrl = this._getContent()?.url;
|
||||
if (typeof mxcUrl === "string") {
|
||||
return this._room.mxcUrl(mxcUrl);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_scaleFactor() {
|
||||
const {info} = this._getContent();
|
||||
const scaleHeightFactor = MAX_HEIGHT / info.h;
|
||||
const scaleWidthFactor = MAX_WIDTH / info.w;
|
||||
const info = this._getContent()?.info;
|
||||
const scaleHeightFactor = MAX_HEIGHT / info?.h;
|
||||
const scaleWidthFactor = MAX_WIDTH / info?.w;
|
||||
// take the smallest scale factor, to respect all constraints
|
||||
// we should not upscale images, so limit scale factor to 1 upwards
|
||||
return Math.min(scaleWidthFactor, scaleHeightFactor, 1);
|
||||
}
|
||||
|
||||
get thumbnailWidth() {
|
||||
const {info} = this._getContent();
|
||||
return Math.round(info.w * this._scaleFactor());
|
||||
const info = this._getContent()?.info;
|
||||
return Math.round(info?.w * this._scaleFactor());
|
||||
}
|
||||
|
||||
get thumbnailHeight() {
|
||||
const {info} = this._getContent();
|
||||
return Math.round(info.h * this._scaleFactor());
|
||||
const info = this._getContent()?.info;
|
||||
return Math.round(info?.h * this._scaleFactor());
|
||||
}
|
||||
|
||||
get label() {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {MessageTile} from "./MessageTile.js";
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SimpleTile} from "./SimpleTile.js";
|
||||
import {getIdentifierColorNumber} from "../../../../avatar.js";
|
||||
|
||||
export class MessageTile extends SimpleTile {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._clock = options.clock;
|
||||
this._isOwn = this._entry.sender === options.ownUserId;
|
||||
this._date = new Date(this._entry.timestamp);
|
||||
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
|
||||
this._isContinuation = false;
|
||||
}
|
||||
|
||||
|
@ -16,12 +34,16 @@ export class MessageTile extends SimpleTile {
|
|||
return this._entry.sender;
|
||||
}
|
||||
|
||||
get senderColorNumber() {
|
||||
return getIdentifierColorNumber(this._entry.sender);
|
||||
}
|
||||
|
||||
get date() {
|
||||
return this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"});
|
||||
return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"});
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this._date.toLocaleTimeString({}, {hour: "numeric", minute: "2-digit"});
|
||||
return this._date && this._date.toLocaleTimeString({}, {hour: "numeric", minute: "2-digit"});
|
||||
}
|
||||
|
||||
get isOwn() {
|
||||
|
@ -38,10 +60,17 @@ export class MessageTile extends SimpleTile {
|
|||
|
||||
updatePreviousSibling(prev) {
|
||||
super.updatePreviousSibling(prev);
|
||||
const isContinuation = prev && prev instanceof MessageTile && prev.sender === this.sender;
|
||||
let isContinuation = false;
|
||||
if (prev && prev instanceof MessageTile && prev.sender === this.sender) {
|
||||
// timestamp is null for pending events
|
||||
const myTimestamp = this._entry.timestamp || this._clock.now();
|
||||
const otherTimestamp = prev._entry.timestamp || this._clock.now();
|
||||
// other message was sent less than 5min ago
|
||||
isContinuation = (myTimestamp - otherTimestamp) < (5 * 60 * 1000);
|
||||
}
|
||||
if (isContinuation !== this._isContinuation) {
|
||||
this._isContinuation = isContinuation;
|
||||
this.emitUpdate("isContinuation");
|
||||
this.emitChange("isContinuation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SimpleTile} from "./SimpleTile.js";
|
||||
|
||||
export class RoomMemberTile extends SimpleTile {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SimpleTile} from "./SimpleTile.js";
|
||||
|
||||
export class RoomNameTile extends SimpleTile {
|
||||
|
|
|
@ -1,9 +1,26 @@
|
|||
import {UpdateAction} from "../UpdateAction.js";
|
||||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
export class SimpleTile {
|
||||
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 {UpdateAction} from "../UpdateAction.js";
|
||||
import {ViewModel} from "../../../../ViewModel.js";
|
||||
|
||||
export class SimpleTile extends ViewModel {
|
||||
constructor({entry}) {
|
||||
super();
|
||||
this._entry = entry;
|
||||
this._emitUpdate = null;
|
||||
}
|
||||
// view model props for all subclasses
|
||||
// hmmm, could also do instanceof ... ?
|
||||
|
@ -22,12 +39,6 @@ export class SimpleTile {
|
|||
return false;
|
||||
}
|
||||
|
||||
emitUpdate(paramName) {
|
||||
if (this._emitUpdate) {
|
||||
this._emitUpdate(this, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
get internalId() {
|
||||
return this._entry.asEventKey().toString();
|
||||
}
|
||||
|
@ -37,7 +48,7 @@ export class SimpleTile {
|
|||
}
|
||||
// TilesCollection contract below
|
||||
setUpdateEmit(emitUpdate) {
|
||||
this._emitUpdate = emitUpdate;
|
||||
this.updateOptions({emitChange: paramName => emitUpdate(this, paramName)});
|
||||
}
|
||||
|
||||
get upperEntry() {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {MessageTile} from "./MessageTile.js";
|
||||
|
||||
export class TextTile extends MessageTile {
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {GapTile} from "./tiles/GapTile.js";
|
||||
import {TextTile} from "./tiles/TextTile.js";
|
||||
import {ImageTile} from "./tiles/ImageTile.js";
|
||||
import {LocationTile} from "./tiles/LocationTile.js";
|
||||
import {RoomNameTile} from "./tiles/RoomNameTile.js";
|
||||
import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
|
||||
import {EncryptedEventTile} from "./tiles/EncryptedEventTile.js";
|
||||
|
||||
export function tilesCreator({room, ownUserId}) {
|
||||
export function tilesCreator({room, ownUserId, clock}) {
|
||||
return function tilesCreator(entry, emitUpdate) {
|
||||
const options = {entry, emitUpdate, ownUserId};
|
||||
const options = {entry, emitUpdate, ownUserId, clock};
|
||||
if (entry.isGap) {
|
||||
return new GapTile(options, room);
|
||||
} else if (entry.eventType) {
|
||||
|
@ -33,6 +50,8 @@ export function tilesCreator({room, ownUserId}) {
|
|||
return new RoomNameTile(options);
|
||||
case "m.room.member":
|
||||
return new RoomMemberTile(options);
|
||||
case "m.room.encrypted":
|
||||
return new EncryptedEventTile(options);
|
||||
default:
|
||||
// unknown type not rendered
|
||||
return null;
|
||||
|
|
|
@ -1,23 +1,60 @@
|
|||
import {avatarInitials} from "../avatar.js";
|
||||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
export class RoomTileViewModel {
|
||||
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 {avatarInitials, getIdentifierColorNumber} from "../../avatar.js";
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
|
||||
export class RoomTileViewModel extends ViewModel {
|
||||
// we use callbacks to parent VM instead of emit because
|
||||
// it would be annoying to keep track of subscriptions in
|
||||
// parent for all RoomTileViewModels
|
||||
// emitUpdate is ObservableMap/ObservableList update mechanism
|
||||
constructor({room, emitUpdate, emitOpen}) {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {room, emitOpen} = options;
|
||||
this._room = room;
|
||||
this._emitUpdate = emitUpdate;
|
||||
this._emitOpen = emitOpen;
|
||||
this._isOpen = false;
|
||||
}
|
||||
|
||||
// called by parent for now (later should integrate with router)
|
||||
close() {
|
||||
if (this._isOpen) {
|
||||
this._isOpen = false;
|
||||
this.emitChange("isOpen");
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this._emitOpen(this._room);
|
||||
this._isOpen = true;
|
||||
this.emitChange("isOpen");
|
||||
this._emitOpen(this._room, this);
|
||||
}
|
||||
|
||||
compare(other) {
|
||||
// sort by name for now
|
||||
return this._room.name.localeCompare(other._room.name);
|
||||
// sort alphabetically
|
||||
const nameCmp = this._room.name.localeCompare(other._room.name);
|
||||
if (nameCmp === 0) {
|
||||
return this._room.id.localeCompare(other._room.id);
|
||||
}
|
||||
return nameCmp;
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
get name() {
|
||||
|
@ -27,4 +64,8 @@ export class RoomTileViewModel {
|
|||
get avatarInitials() {
|
||||
return avatarInitials(this._room.name);
|
||||
}
|
||||
|
||||
get avatarColorNumber() {
|
||||
return getIdentifierColorNumber(this._room.id)
|
||||
}
|
||||
}
|
||||
|
|
26
src/legacy-polyfill.js
Normal file
26
src/legacy-polyfill.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// polyfills needed for IE11
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
import "mdn-polyfills/Element.prototype.closest";
|
||||
// TODO: contribute this to mdn-polyfills
|
||||
if (!Element.prototype.remove) {
|
||||
Element.prototype.remove = function remove() {
|
||||
this.parentNode.removeChild(this);
|
||||
};
|
||||
}
|
37
src/main.js
37
src/main.js
|
@ -1,5 +1,23 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 {RecordRequester, ReplayRequester} from "./matrix/net/request/replay.js";
|
||||
import {fetchRequest} from "./matrix/net/request/fetch.js";
|
||||
import {createFetchRequest} from "./matrix/net/request/fetch.js";
|
||||
import {xhrRequest} from "./matrix/net/request/xhr.js";
|
||||
import {SessionContainer} from "./matrix/SessionContainer.js";
|
||||
import {StorageFactory} from "./matrix/storage/idb/StorageFactory.js";
|
||||
import {SessionInfoStorage} from "./matrix/sessioninfo/localstorage/SessionInfoStorage.js";
|
||||
|
@ -8,7 +26,10 @@ import {BrawlView} from "./ui/web/BrawlView.js";
|
|||
import {Clock} from "./ui/web/dom/Clock.js";
|
||||
import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
|
||||
|
||||
export default async function main(container) {
|
||||
// Don't use a default export here, as we use multiple entries during legacy build,
|
||||
// which does not support default exports,
|
||||
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
||||
export async function main(container) {
|
||||
try {
|
||||
// to replay:
|
||||
// const fetchLog = await (await fetch("/fetchlogs/constrainterror.json")).json();
|
||||
|
@ -16,13 +37,17 @@ export default async function main(container) {
|
|||
// const request = replay.request;
|
||||
|
||||
// to record:
|
||||
// const recorder = new RecordRequester(fetchRequest);
|
||||
// const recorder = new RecordRequester(createFetchRequest(clock.createTimeout));
|
||||
// const request = recorder.request;
|
||||
// window.getBrawlFetchLog = () => recorder.log();
|
||||
// normal network:
|
||||
const request = fetchRequest;
|
||||
const sessionInfoStorage = new SessionInfoStorage("brawl_sessions_v1");
|
||||
const clock = new Clock();
|
||||
let request;
|
||||
if (typeof fetch === "function") {
|
||||
request = createFetchRequest(clock.createTimeout);
|
||||
} else {
|
||||
request = xhrRequest;
|
||||
}
|
||||
const sessionInfoStorage = new SessionInfoStorage("brawl_sessions_v1");
|
||||
const storageFactory = new StorageFactory();
|
||||
|
||||
const vm = new BrawlViewModel({
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Platform} from "../Platform.js";
|
||||
import {HomeServerError, ConnectionError} from "./error.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Room} from "./room/Room.js";
|
||||
import { ObservableMap } from "../observable/index.js";
|
||||
import { SendScheduler, RateLimitingBackoff } from "./SendScheduler.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {createEnum} from "../utils/enum.js";
|
||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
||||
import {HomeServerApi} from "./net/HomeServerApi.js";
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 {AbortError} from "./error.js";
|
||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
||||
import {createEnum} from "../utils/enum.js";
|
||||
|
@ -27,6 +44,15 @@ function parseRooms(roomsSection, roomCallback) {
|
|||
return [];
|
||||
}
|
||||
|
||||
function timelineIsEmpty(roomResponse) {
|
||||
try {
|
||||
const events = roomResponse?.timeline?.events;
|
||||
return Array.isArray(events) && events.length === 0;
|
||||
} catch (err) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class Sync {
|
||||
constructor({hsApi, session, storage}) {
|
||||
this._hsApi = hsApi;
|
||||
|
@ -86,6 +112,7 @@ export class Sync {
|
|||
const totalRequestTimeout = timeout + (80 * 1000); // same as riot-web, don't get stuck on wedged long requests
|
||||
this._currentRequest = this._hsApi.sync(syncToken, syncFilterId, timeout, {timeout: totalRequestTimeout});
|
||||
const response = await this._currentRequest.response();
|
||||
const isInitialSync = !syncToken;
|
||||
syncToken = response.next_batch;
|
||||
const storeNames = this._storage.storeNames;
|
||||
const syncTxn = await this._storage.readWriteTxn([
|
||||
|
@ -105,6 +132,11 @@ export class Sync {
|
|||
// presence
|
||||
if (response.rooms) {
|
||||
const promises = parseRooms(response.rooms, async (roomId, roomResponse, membership) => {
|
||||
// ignore rooms with empty timelines during initial sync,
|
||||
// see https://github.com/vector-im/hydrogen-web/issues/15
|
||||
if (isInitialSync && timelineIsEmpty(roomResponse)) {
|
||||
return;
|
||||
}
|
||||
let room = this._session.rooms.get(roomId);
|
||||
if (!room) {
|
||||
room = this._session.createRoom(roomId);
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class User {
|
||||
constructor(userId) {
|
||||
this._userId = userId;
|
||||
|
|
|
@ -1,3 +1,30 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class WrappedError extends Error {
|
||||
constructor(message, cause) {
|
||||
super(`${message}: ${cause.message}`);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "WrappedError";
|
||||
}
|
||||
}
|
||||
|
||||
export class HomeServerError extends Error {
|
||||
constructor(method, url, body, status) {
|
||||
super(`${body ? body.error : status} on ${method} ${url}`);
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {AbortError} from "../../utils/error.js";
|
||||
|
||||
export class ExponentialRetryDelay {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {
|
||||
HomeServerError,
|
||||
ConnectionError,
|
||||
|
@ -5,9 +21,9 @@ import {
|
|||
} from "../error.js";
|
||||
|
||||
class RequestWrapper {
|
||||
constructor(method, url, requestResult, responsePromise) {
|
||||
constructor(method, url, requestResult) {
|
||||
this._requestResult = requestResult;
|
||||
this._promise = responsePromise.then(response => {
|
||||
this._promise = requestResult.response().then(response => {
|
||||
// ok?
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.body;
|
||||
|
@ -44,35 +60,6 @@ export class HomeServerApi {
|
|||
return `${this._homeserver}/_matrix/client/r0${csPath}`;
|
||||
}
|
||||
|
||||
_abortOnTimeout(timeoutAmount, requestResult, responsePromise) {
|
||||
const timeout = this._createTimeout(timeoutAmount);
|
||||
// abort request if timeout finishes first
|
||||
let timedOut = false;
|
||||
timeout.elapsed().then(
|
||||
() => {
|
||||
timedOut = true;
|
||||
requestResult.abort();
|
||||
},
|
||||
() => {} // ignore AbortError
|
||||
);
|
||||
// abort timeout if request finishes first
|
||||
return responsePromise.then(
|
||||
response => {
|
||||
timeout.abort();
|
||||
return response;
|
||||
},
|
||||
err => {
|
||||
timeout.abort();
|
||||
// map error to TimeoutError
|
||||
if (err instanceof AbortError && timedOut) {
|
||||
throw new ConnectionError(`Request timed out after ${timeoutAmount}ms`, true);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_encodeQueryParams(queryParams) {
|
||||
return Object.entries(queryParams || {})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
|
@ -102,19 +89,10 @@ export class HomeServerApi {
|
|||
method,
|
||||
headers,
|
||||
body: bodyString,
|
||||
timeout: options && options.timeout
|
||||
});
|
||||
|
||||
let responsePromise = requestResult.response();
|
||||
|
||||
if (options && options.timeout) {
|
||||
responsePromise = this._abortOnTimeout(
|
||||
options.timeout,
|
||||
requestResult,
|
||||
responsePromise
|
||||
);
|
||||
}
|
||||
|
||||
const wrapper = new RequestWrapper(method, url, requestResult, responsePromise);
|
||||
const wrapper = new RequestWrapper(method, url, requestResult);
|
||||
|
||||
if (this._reconnector) {
|
||||
wrapper.response().catch(err => {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {createEnum} from "../../utils/enum.js";
|
||||
import {ObservableValue} from "../../observable/ObservableValue.js";
|
||||
|
||||
|
|
|
@ -1,7 +1,25 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
} from "../../error.js";
|
||||
import {abortOnTimeout} from "../timeout.js";
|
||||
|
||||
class RequestResult {
|
||||
constructor(promise, controller) {
|
||||
|
@ -15,9 +33,9 @@ class RequestResult {
|
|||
}
|
||||
};
|
||||
});
|
||||
this._promise = Promise.race([promise, abortPromise]);
|
||||
this.promise = Promise.race([promise, abortPromise]);
|
||||
} else {
|
||||
this._promise = promise;
|
||||
this.promise = promise;
|
||||
this._controller = controller;
|
||||
}
|
||||
}
|
||||
|
@ -27,47 +45,55 @@ class RequestResult {
|
|||
}
|
||||
|
||||
response() {
|
||||
return this._promise;
|
||||
return this.promise;
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchRequest(url, options) {
|
||||
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
||||
if (controller) {
|
||||
export function createFetchRequest(createTimeout) {
|
||||
return function fetchRequest(url, options) {
|
||||
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
||||
if (controller) {
|
||||
options = Object.assign(options, {
|
||||
signal: controller.signal
|
||||
});
|
||||
}
|
||||
options = Object.assign(options, {
|
||||
signal: controller.signal
|
||||
mode: "cors",
|
||||
credentials: "omit",
|
||||
referrer: "no-referrer",
|
||||
cache: "no-cache",
|
||||
});
|
||||
}
|
||||
options = Object.assign(options, {
|
||||
mode: "cors",
|
||||
credentials: "omit",
|
||||
referrer: "no-referrer",
|
||||
cache: "no-cache",
|
||||
});
|
||||
if (options.headers) {
|
||||
const headers = new Headers();
|
||||
for(const [name, value] of options.headers.entries()) {
|
||||
headers.append(name, value);
|
||||
if (options.headers) {
|
||||
const headers = new Headers();
|
||||
for(const [name, value] of options.headers.entries()) {
|
||||
headers.append(name, value);
|
||||
}
|
||||
options.headers = headers;
|
||||
}
|
||||
options.headers = headers;
|
||||
}
|
||||
const promise = fetch(url, options).then(async response => {
|
||||
const {status} = response;
|
||||
const body = await response.json();
|
||||
return {status, body};
|
||||
}, err => {
|
||||
if (err.name === "AbortError") {
|
||||
throw new AbortError();
|
||||
} else if (err instanceof TypeError) {
|
||||
// Network errors are reported as TypeErrors, see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
|
||||
// this can either mean user is offline, server is offline, or a CORS error (server misconfiguration).
|
||||
//
|
||||
// One could check navigator.onLine to rule out the first
|
||||
// but the 2 latter ones are indistinguishable from javascript.
|
||||
throw new ConnectionError(`${options.method} ${url}: ${err.message}`);
|
||||
const promise = fetch(url, options).then(async response => {
|
||||
const {status} = response;
|
||||
const body = await response.json();
|
||||
return {status, body};
|
||||
}, err => {
|
||||
if (err.name === "AbortError") {
|
||||
throw new AbortError();
|
||||
} else if (err instanceof TypeError) {
|
||||
// Network errors are reported as TypeErrors, see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
|
||||
// this can either mean user is offline, server is offline, or a CORS error (server misconfiguration).
|
||||
//
|
||||
// One could check navigator.onLine to rule out the first
|
||||
// but the 2 latter ones are indistinguishable from javascript.
|
||||
throw new ConnectionError(`${options.method} ${url}: ${err.message}`);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
const result = new RequestResult(promise, controller);
|
||||
|
||||
if (options.timeout) {
|
||||
result.promise = abortOnTimeout(createTimeout, options.timeout, result, result.promise);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
return new RequestResult(promise, controller);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
|
|
97
src/matrix/net/request/xhr.js
Normal file
97
src/matrix/net/request/xhr.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
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 {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
} from "../../error.js";
|
||||
|
||||
class RequestResult {
|
||||
constructor(promise, xhr) {
|
||||
this._promise = promise;
|
||||
this._xhr = xhr;
|
||||
}
|
||||
|
||||
abort() {
|
||||
this._xhr.abort();
|
||||
}
|
||||
|
||||
response() {
|
||||
return this._promise;
|
||||
}
|
||||
}
|
||||
|
||||
function send(url, options) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(options.method, url);
|
||||
if (options.headers) {
|
||||
for(const [name, value] of options.headers.entries()) {
|
||||
xhr.setRequestHeader(name, value);
|
||||
}
|
||||
}
|
||||
if (options.timeout) {
|
||||
xhr.timeout = options.timeout;
|
||||
}
|
||||
|
||||
xhr.send(options.body || null);
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function xhrAsPromise(xhr, method, url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.addEventListener("load", () => resolve(xhr));
|
||||
xhr.addEventListener("abort", () => reject(new AbortError()));
|
||||
xhr.addEventListener("error", () => reject(new ConnectionError(`Error ${method} ${url}`)));
|
||||
xhr.addEventListener("timeout", () => reject(new ConnectionError(`Timeout ${method} ${url}`, true)));
|
||||
});
|
||||
}
|
||||
|
||||
function addCacheBuster(urlStr, random = Math.random) {
|
||||
// XHR doesn't have a good way to disable cache,
|
||||
// so add a random query param
|
||||
// see https://davidtranscend.com/blog/prevent-ie11-cache-ajax-requests/
|
||||
if (urlStr.includes("?")) {
|
||||
urlStr = urlStr + "&";
|
||||
} else {
|
||||
urlStr = urlStr + "?";
|
||||
}
|
||||
return urlStr + `_cacheBuster=${Math.ceil(random() * Number.MAX_SAFE_INTEGER)}`;
|
||||
}
|
||||
|
||||
export function xhrRequest(url, options) {
|
||||
url = addCacheBuster(url);
|
||||
const xhr = send(url, options);
|
||||
const promise = xhrAsPromise(xhr, options.method, url).then(xhr => {
|
||||
const {status} = xhr;
|
||||
let body = xhr.responseText;
|
||||
if (xhr.getResponseHeader("Content-Type") === "application/json") {
|
||||
body = JSON.parse(body);
|
||||
}
|
||||
return {status, body};
|
||||
});
|
||||
return new RequestResult(promise, xhr);
|
||||
}
|
||||
|
||||
export function tests() {
|
||||
return {
|
||||
"add cache buster": assert => {
|
||||
const random = () => 0.5;
|
||||
assert.equal(addCacheBuster("http://foo", random), "http://foo?_cacheBuster=4503599627370496");
|
||||
assert.equal(addCacheBuster("http://foo?bar=baz", random), "http://foo?bar=baz&_cacheBuster=4503599627370496");
|
||||
}
|
||||
}
|
||||
}
|
51
src/matrix/net/timeout.js
Normal file
51
src/matrix/net/timeout.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
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 {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
} from "../error.js";
|
||||
|
||||
|
||||
export function abortOnTimeout(createTimeout, timeoutAmount, requestResult, responsePromise) {
|
||||
const timeout = createTimeout(timeoutAmount);
|
||||
// abort request if timeout finishes first
|
||||
let timedOut = false;
|
||||
timeout.elapsed().then(
|
||||
() => {
|
||||
timedOut = true;
|
||||
requestResult.abort();
|
||||
},
|
||||
() => {} // ignore AbortError when timeout is aborted
|
||||
);
|
||||
// abort timeout if request finishes first
|
||||
return responsePromise.then(
|
||||
response => {
|
||||
timeout.abort();
|
||||
return response;
|
||||
},
|
||||
err => {
|
||||
timeout.abort();
|
||||
// map error to TimeoutError
|
||||
if (err instanceof AbortError && timedOut) {
|
||||
throw new ConnectionError(`Request timed out after ${timeoutAmount}ms`, true);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {EventEmitter} from "../../utils/EventEmitter.js";
|
||||
import {RoomSummary} from "./RoomSummary.js";
|
||||
import {SyncWriter} from "./timeline/persistence/SyncWriter.js";
|
||||
|
@ -5,6 +21,7 @@ import {GapWriter} from "./timeline/persistence/GapWriter.js";
|
|||
import {Timeline} from "./timeline/Timeline.js";
|
||||
import {FragmentIdComparer} from "./timeline/FragmentIdComparer.js";
|
||||
import {SendQueue} from "./sending/SendQueue.js";
|
||||
import {WrappedError} from "../error.js"
|
||||
|
||||
export class Room extends EventEmitter {
|
||||
constructor({roomId, storage, hsApi, emitCollectionChange, sendScheduler, pendingEvents, user}) {
|
||||
|
@ -51,8 +68,12 @@ export class Room extends EventEmitter {
|
|||
}
|
||||
|
||||
load(summary, txn) {
|
||||
this._summary.load(summary);
|
||||
return this._syncWriter.load(txn);
|
||||
try {
|
||||
this._summary.load(summary);
|
||||
return this._syncWriter.load(txn);
|
||||
} catch (err) {
|
||||
throw new WrappedError(`Could not load room ${this._roomId}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(eventType, content) {
|
||||
|
@ -96,6 +117,9 @@ export class Room extends EventEmitter {
|
|||
|
||||
/** @public */
|
||||
async fillGap(fragmentEntry, amount) {
|
||||
if (fragmentEntry.edgeReached) {
|
||||
return;
|
||||
}
|
||||
const response = await this._hsApi.messages(this._roomId, {
|
||||
from: fragmentEntry.token,
|
||||
dir: fragmentEntry.direction.asApiString(),
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
function applySyncResponse(data, roomResponse, membership) {
|
||||
if (roomResponse.summary) {
|
||||
data = updateSummary(data, roomResponse.summary);
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class PendingEvent {
|
||||
constructor(data) {
|
||||
this._data = data;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SortedArray} from "../../../observable/list/SortedArray.js";
|
||||
import {ConnectionError} from "../../error.js";
|
||||
import {PendingEvent} from "./PendingEvent.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class Direction {
|
||||
constructor(isForward) {
|
||||
this._isForward = isForward;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Platform} from "../../../Platform.js";
|
||||
|
||||
// key for events in the timelineEvents store
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
lookups will be far more frequent than changing fragment order,
|
||||
so data structure should be optimized for fast lookup
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SortedArray, MappedList, ConcatList} from "../../../observable/index.js";
|
||||
import {Direction} from "./Direction.js";
|
||||
import {TimelineReader} from "./persistence/TimelineReader.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function isValidFragmentId(id) {
|
||||
return typeof id === "number";
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
//entries can be sorted, first by fragment, then by entry index.
|
||||
import {EventKey} from "../EventKey.js";
|
||||
export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseEntry} from "./BaseEntry.js";
|
||||
|
||||
export class EventEntry extends BaseEntry {
|
||||
|
@ -19,8 +35,7 @@ export class EventEntry extends BaseEntry {
|
|||
}
|
||||
|
||||
get prevContent() {
|
||||
const unsigned = this._eventEntry.event.unsigned;
|
||||
return unsigned && unsigned.prev_content;
|
||||
return this._eventEntry.event.unsigned?.prev_content;
|
||||
}
|
||||
|
||||
get eventType() {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseEntry} from "./BaseEntry.js";
|
||||
import {Direction} from "../Direction.js";
|
||||
import {isValidFragmentId} from "../common.js";
|
||||
|
@ -44,7 +60,7 @@ export class FragmentBoundaryEntry extends BaseEntry {
|
|||
}
|
||||
|
||||
get isGap() {
|
||||
return !!this.token;
|
||||
return !!this.token && !this.edgeReached;
|
||||
}
|
||||
|
||||
get token() {
|
||||
|
@ -63,6 +79,25 @@ export class FragmentBoundaryEntry extends BaseEntry {
|
|||
}
|
||||
}
|
||||
|
||||
get edgeReached() {
|
||||
if (this.started) {
|
||||
return this.fragment.startReached;
|
||||
} else {
|
||||
return this.fragment.endReached;
|
||||
}
|
||||
}
|
||||
|
||||
set edgeReached(reached) {
|
||||
|
||||
if (this.started) {
|
||||
this.fragment.startReached = reached;
|
||||
} else {
|
||||
this.fragment.endReached = reached;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
get linkedFragmentId() {
|
||||
if (this.started) {
|
||||
return this.fragment.previousId;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseEntry, PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
||||
|
||||
export class PendingEventEntry extends BaseEntry {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {EventKey} from "../EventKey.js";
|
||||
import {EventEntry} from "../entries/EventEntry.js";
|
||||
import {createEventEntry, directionalAppend} from "./common.js";
|
||||
|
@ -162,6 +178,14 @@ export class GapWriter {
|
|||
if (fragmentEntry.token !== start) {
|
||||
throw new Error("start is not equal to prev_batch or next_batch");
|
||||
}
|
||||
|
||||
// begin (or end) of timeline reached
|
||||
if (chunk.length === 0) {
|
||||
fragmentEntry.edgeReached = true;
|
||||
await txn.timelineFragments.update(fragmentEntry.fragment);
|
||||
return {entries: [fragmentEntry], fragments: []};
|
||||
}
|
||||
|
||||
// find last event in fragment so we get the eventIndex to begin creating keys at
|
||||
let lastKey = await this._findFragmentEdgeEventKey(fragmentEntry, txn);
|
||||
// find out if any event in chunk is already present using findFirstOrLastOccurringEventId
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {EventKey} from "../EventKey.js";
|
||||
import {EventEntry} from "../entries/EventEntry.js";
|
||||
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {directionalConcat, directionalAppend} from "./common.js";
|
||||
import {Direction} from "../Direction.js";
|
||||
import {EventEntry} from "../entries/EventEntry.js";
|
||||
|
@ -44,8 +60,8 @@ export class TimelineReader {
|
|||
let fragmentEntry = new FragmentBoundaryEntry(fragment, direction.isBackward, this._fragmentIdComparer);
|
||||
// append or prepend fragmentEntry, reuse func from GapWriter?
|
||||
directionalAppend(entries, fragmentEntry, direction);
|
||||
// don't count it in amount perhaps? or do?
|
||||
if (fragmentEntry.hasLinkedFragment) {
|
||||
// only continue loading if the fragment boundary can't be backfilled
|
||||
if (!fragmentEntry.token && fragmentEntry.hasLinkedFragment) {
|
||||
const nextFragment = await fragmentStore.get(this._roomId, fragmentEntry.linkedFragmentId);
|
||||
this._fragmentIdComparer.add(nextFragment);
|
||||
const nextFragmentEntry = new FragmentBoundaryEntry(nextFragment, direction.isForward, this._fragmentIdComparer);
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function createEventEntry(key, roomId, event) {
|
||||
return {
|
||||
fragmentId: key.fragmentId,
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class SessionInfoStorage {
|
||||
constructor(name) {
|
||||
this._name = name;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export const STORE_NAMES = Object.freeze([
|
||||
"session",
|
||||
"roomState",
|
||||
|
@ -24,6 +40,11 @@ export class StorageError extends Error {
|
|||
if (typeof cause.code === "number") {
|
||||
fullMessage += `(code: ${cause.name}) `;
|
||||
}
|
||||
}
|
||||
if (value) {
|
||||
fullMessage += `(value: ${JSON.stringify(value)}) `;
|
||||
}
|
||||
if (cause) {
|
||||
fullMessage += cause.message;
|
||||
}
|
||||
super(fullMessage);
|
||||
|
@ -33,4 +54,8 @@ export class StorageError extends Error {
|
|||
this.cause = cause;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "StorageError";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {iterateCursor, reqAsPromise} from "./utils.js";
|
||||
|
||||
export class QueryTarget {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Transaction} from "./Transaction.js";
|
||||
import { STORE_NAMES, StorageError } from "../common.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Storage} from "./Storage.js";
|
||||
import { openDatabase, reqAsPromise } from "./utils.js";
|
||||
import { exportSession, importSession } from "./export.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {QueryTarget} from "./QueryTarget.js";
|
||||
import { reqAsPromise } from "./utils.js";
|
||||
import { StorageError } from "../common.js";
|
||||
|
@ -98,7 +114,7 @@ export class Store extends QueryTarget {
|
|||
return await reqAsPromise(this._idbStore.put(value));
|
||||
} catch(err) {
|
||||
const originalErr = err.cause;
|
||||
throw new StorageError(`put on ${this._idbStore.name} failed`, originalErr, value);
|
||||
throw new StorageError(`put on ${err.databaseName}.${err.storeName} failed`, originalErr, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,11 +123,17 @@ export class Store extends QueryTarget {
|
|||
return await reqAsPromise(this._idbStore.add(value));
|
||||
} catch(err) {
|
||||
const originalErr = err.cause;
|
||||
throw new StorageError(`add on ${this._idbStore.name} failed`, originalErr, value);
|
||||
throw new StorageError(`add on ${err.databaseName}.${err.storeName} failed`, originalErr, value);
|
||||
}
|
||||
}
|
||||
|
||||
delete(keyOrKeyRange) {
|
||||
return reqAsPromise(this._idbStore.delete(keyOrKeyRange));
|
||||
async delete(keyOrKeyRange) {
|
||||
try {
|
||||
return await reqAsPromise(this._idbStore.delete(keyOrKeyRange));
|
||||
} catch(err) {
|
||||
const originalErr = err.cause;
|
||||
throw new StorageError(`delete on ${err.databaseName}.${err.storeName} failed`, originalErr, keyOrKeyRange);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {txnAsPromise} from "./utils.js";
|
||||
import {StorageError} from "../common.js";
|
||||
import {Store} from "./Store.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 { iterateCursor, txnAsPromise } from "./utils.js";
|
||||
import { STORE_NAMES } from "../common.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 { encodeUint32, decodeUint32 } from "../utils.js";
|
||||
import {Platform} from "../../../../Platform.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class RoomStateStore {
|
||||
constructor(idbStore) {
|
||||
this._roomStateStore = idbStore;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
store contains:
|
||||
roomId
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
store contains:
|
||||
loginData {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {EventKey} from "../../../room/timeline/EventKey.js";
|
||||
import { StorageError } from "../../common.js";
|
||||
import { encodeUint32 } from "../utils.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 { StorageError } from "../../common.js";
|
||||
import {Platform} from "../../../../Platform.js";
|
||||
import { encodeUint32 } from "../utils.js";
|
||||
|
|
|
@ -1,5 +1,31 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 { StorageError } from "../common.js";
|
||||
|
||||
class WrappedDOMException extends StorageError {
|
||||
constructor(request) {
|
||||
const source = request?.source;
|
||||
const storeName = source?.name || "<unknown store>";
|
||||
const databaseName = source?.transaction?.db?.name || "<unknown db>";
|
||||
super(`Failed IDBRequest on ${databaseName}.${storeName}`, request.error);
|
||||
this.storeName = storeName;
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
}
|
||||
|
||||
// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb
|
||||
export function encodeUint32(n) {
|
||||
|
@ -22,21 +48,17 @@ 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(wrapError(event.target.error)));
|
||||
req.addEventListener("error", event => reject(new WrappedDOMException(event.target)));
|
||||
});
|
||||
}
|
||||
|
||||
export function txnAsPromise(txn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.addEventListener("complete", resolve);
|
||||
txn.addEventListener("abort", event => reject(wrapError(event.target.error)));
|
||||
txn.addEventListener("abort", event => reject(new WrappedDOMException(event.target)));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {Transaction} from "./Transaction.js";
|
||||
import { STORE_MAP, STORE_NAMES } from "../common.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {RoomTimelineStore} from "./stores/RoomTimelineStore.js";
|
||||
|
||||
export class Transaction {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SortKey} from "../../room/timeline/SortKey.js";
|
||||
import {sortedIndex} from "../../../utils/sortedIndex.js";
|
||||
import {Store} from "./Store.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class Store {
|
||||
constructor(storeValue, writable) {
|
||||
this._storeValue = storeValue;
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {ObservableValue} from "../observable/ObservableValue.js";
|
||||
|
||||
class Timeout {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export class BaseObservable {
|
||||
constructor() {
|
||||
this._handlers = new Set();
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {AbortError} from "../utils/error.js";
|
||||
import {BaseObservable} from "./BaseObservable.js";
|
||||
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {SortedMapList} from "./list/SortedMapList.js";
|
||||
import {FilteredMap} from "./map/FilteredMap.js";
|
||||
import {MappedMap} from "./map/MappedMap.js";
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservable} from "../BaseObservable.js";
|
||||
|
||||
export class BaseObservableList extends BaseObservable {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservableList} from "./BaseObservableList.js";
|
||||
|
||||
export class ConcatList extends BaseObservableList {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservableList} from "./BaseObservableList.js";
|
||||
|
||||
export class MappedList extends BaseObservableList {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservableList} from "./BaseObservableList.js";
|
||||
|
||||
export class ObservableArray extends BaseObservableList {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
|
||||
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 {BaseObservableList} from "./BaseObservableList.js";
|
||||
import {sortedIndex} from "../../utils/sortedIndex.js";
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue