Compare commits

...
This repository has been archived on 2022-08-19. You can view files and clone it, but cannot push or open issues or pull requests.

7 commits

Author SHA1 Message Date
Danila Fedorin
a7adc27a69 Try switching some files over to TypeScript 2021-08-05 17:03:46 -07:00
Danila Fedorin
d5503b2e6b Rely on snowpack to serve node modules 2021-08-04 16:31:05 -07:00
Danila Fedorin
c5153339c0 Ignore seemingly dead code 2021-08-04 16:19:14 -07:00
Danila Fedorin
b5b4d563ec Add index file to a subfolder to avoid exposing base directory 2021-08-02 17:19:56 -07:00
Danila Fedorin
59c1a1f401 Add a configuration file and 'migrate' deserialize to TS 2021-08-02 17:18:00 -07:00
Danila Fedorin
3687f1fd4a Copy instead of symlinking since it seems snowpack can't pick up symlinks 2021-08-02 17:00:40 -07:00
Danila Fedorin
32b308faa7 Add snowpack and typescript as dev dependencies 2021-08-02 16:59:04 -07:00
18 changed files with 2489 additions and 74 deletions

View file

@ -51,6 +51,8 @@
"rollup": "^2.26.4", "rollup": "^2.26.4",
"rollup-plugin-cleanup": "^3.1.1", "rollup-plugin-cleanup": "^3.1.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"snowpack": "^3.8.3",
"typescript": "^4.3.5",
"xxhashjs": "^0.2.2" "xxhashjs": "^0.2.2"
}, },
"dependencies": { "dependencies": {

41
public/index.html Normal file
View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<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="Hydrogen Chat">
<meta name="description" content="A matrix chat application">
<link rel="apple-touch-icon" href="assets/icon-maskable.png">
<link rel="stylesheet" type="text/css" href="src/platform/web/ui/css/main.css">
<link rel="stylesheet" type="text/css" href="src/platform/web/ui/css/themes/element/theme.css" title="Element Theme">
<link rel="alternate stylesheet" type="text/css" href="src/platform/web/ui/css/themes/bubbles/theme.css" title="Bubbles Theme">
</head>
<body class="hydrogen">
<script id="version" type="disabled">
window.HYDROGEN_VERSION = "%%VERSION%%";
window.HYDROGEN_GLOBAL_HASH = "%%GLOBAL_HASH%%";
</script>
<script id="main" type="module">
import {main} from "./src/main.js";
import {Platform} from "./src/platform/web/Platform.js";
main(new Platform(document.body, {
worker: "src/worker.js",
downloadSandbox: "assets/download-sandbox.html",
defaultHomeServer: "matrix.org",
// NOTE: uncomment this if you want the service worker for local development
// serviceWorker: "sw.js",
// NOTE: provide push config if you want push notifs for local development
// see assets/config.json for what the config looks like
// push: {...},
olm: {
wasm: "lib/olm/olm.wasm",
legacyBundle: "lib/olm/olm_legacy.js",
wasmBundle: "lib/olm/olm.js",
}
}, null, {development: true}));
</script>
</body>
</html>

View file

@ -75,7 +75,7 @@ async function populateLib() {
const olmDstDir = path.join(libDir, "olm/"); const olmDstDir = path.join(libDir, "olm/");
await fs.mkdir(olmDstDir); await fs.mkdir(olmDstDir);
for (const file of ["olm.js", "olm.wasm", "olm_legacy.js"]) { for (const file of ["olm.js", "olm.wasm", "olm_legacy.js"]) {
await fs.symlink(path.join(olmSrcDir, file), path.join(olmDstDir, file)); await fs.copyFile(path.join(olmSrcDir, file), path.join(olmDstDir, file));
} }
// transpile node-html-parser to esm // transpile node-html-parser to esm
await fs.mkdir(path.join(libDir, "node-html-parser/")); await fs.mkdir(path.join(libDir, "node-html-parser/"));
@ -85,7 +85,7 @@ async function populateLib() {
); );
// Symlink dompurify // Symlink dompurify
await fs.mkdir(path.join(libDir, "dompurify/")); await fs.mkdir(path.join(libDir, "dompurify/"));
await fs.symlink( await fs.copyFile(
require.resolve('dompurify/dist/purify.es.js'), require.resolve('dompurify/dist/purify.es.js'),
path.join(libDir, "dompurify/index.js") path.join(libDir, "dompurify/index.js")
); );

35
snowpack.config.js Normal file
View file

@ -0,0 +1,35 @@
// Snowpack Configuration File
// See all supported options: https://www.snowpack.dev/reference/configuration
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
"src": "/src",
"public": "/",
"lib": {url: "/lib", static: true },
/* ... */
},
exclude: [
/* Avoid scanning scripts which use dev-dependencies and pull in babel, rollup, etc. */
'**/node_modules/**/*', '**/scripts/**', '**/target/**', '**/prototypes/**', '**/src/matrix/storage/memory/**'
],
plugins: [
/* ... */
],
packageOptions: {
/* ... */
external: [
/* Olm seems to import these but not use them. */
"path",
"crypto",
"fs",
],
},
devOptions: {
open: "none",
/* ... */
},
buildOptions: {
/* ... */
},
};

View file

@ -343,7 +343,7 @@ export function parseHTMLBody(platform, mediaRepository, html) {
return new MessageBody(html, parts); return new MessageBody(html, parts);
} }
import parse from '../../../../../lib/node-html-parser/index.js'; import parse from 'node-html-parser';
export function tests() { export function tests() {
class HTMLParseResult { class HTMLParseResult {

View file

@ -16,7 +16,7 @@ limitations under the License.
import {BaseTextTile, BodyFormat} from "./BaseTextTile.js"; import {BaseTextTile, BodyFormat} from "./BaseTextTile.js";
import {parsePlainBody} from "../MessageBody.js"; import {parsePlainBody} from "../MessageBody.js";
import {parseHTMLBody} from "../deserialize.js"; import {parseHTMLBody} from "../deserialize.ts";
export class TextTile extends BaseTextTile { export class TextTile extends BaseTextTile {
_getContentString(key) { _getContentString(key) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import anotherjson from "../../../lib/another-json/index.js"; import anotherjson from "another-json";
import {SESSION_KEY_PREFIX, OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./common.js"; import {SESSION_KEY_PREFIX, OLM_ALGORITHM, MEGOLM_ALGORITHM} from "./common.js";
// use common prefix so it's easy to clear properties that are not e2ee related during session clear // use common prefix so it's easy to clear properties that are not e2ee related during session clear

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import anotherjson from "../../../lib/another-json/index.js"; import anotherjson from "another-json";
import {createEnum} from "../../utils/enum.js"; import {createEnum} from "../../utils/enum.js";
export const DecryptionSource = createEnum("Sync", "Timeline", "Retry"); export const DecryptionSource = createEnum("Sync", "Timeline", "Retry");

View file

@ -18,20 +18,26 @@ limitations under the License.
import {EventKey} from "../EventKey.js"; import {EventKey} from "../EventKey.js";
export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER; export const PENDING_FRAGMENT_ID = Number.MAX_SAFE_INTEGER;
export class BaseEntry { export interface FragmentIdComparer {
compare(a: number, b: number): number;
}
export abstract class BaseEntry {
protected _fragmentIdComparer: FragmentIdComparer
constructor(fragmentIdComparer) { constructor(fragmentIdComparer) {
this._fragmentIdComparer = fragmentIdComparer; this._fragmentIdComparer = fragmentIdComparer;
} }
get fragmentId() { get fragmentId(): number {
throw new Error("unimplemented"); throw new Error("unimplemented");
} }
get entryIndex() { get entryIndex(): number {
throw new Error("unimplemented"); throw new Error("unimplemented");
} }
compare(otherEntry) { compare(otherEntry: BaseEntry): number {
if (this.fragmentId === otherEntry.fragmentId) { if (this.fragmentId === otherEntry.fragmentId) {
return this.entryIndex - otherEntry.entryIndex; return this.entryIndex - otherEntry.entryIndex;
} else if (this.fragmentId === PENDING_FRAGMENT_ID) { } else if (this.fragmentId === PENDING_FRAGMENT_ID) {
@ -44,9 +50,9 @@ export class BaseEntry {
} }
} }
asEventKey() { asEventKey(): EventKey {
return new EventKey(this.fragmentId, this.entryIndex); return new EventKey(this.fragmentId, this.entryIndex);
} }
updateFrom() {} updateFrom(other: BaseEntry) {}
} }

View file

@ -14,33 +14,48 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseEntry} from "./BaseEntry.js"; import {FragmentIdComparer, BaseEntry} from "./BaseEntry";
import {PendingEventEntry} from "./PendingEventEntry.js"
import {REDACTION_TYPE} from "../../common.js"; import {REDACTION_TYPE} from "../../common.js";
import {createAnnotation, ANNOTATION_RELATION_TYPE, getRelationFromContent} from "../relations.js"; import {createAnnotation, ANNOTATION_RELATION_TYPE, getRelationFromContent} from "../relations.js";
import {PendingAnnotation} from "../PendingAnnotation.js"; import {PendingAnnotation} from "../PendingAnnotation.js";
export interface Annotation {
count: number,
me: boolean,
firstTimestamp: number
}
/** Deals mainly with local echo for relations and redactions, /** Deals mainly with local echo for relations and redactions,
* so it is shared between PendingEventEntry and EventEntry */ * so it is shared between PendingEventEntry and EventEntry */
export class BaseEventEntry extends BaseEntry { export abstract class BaseEventEntry extends BaseEntry {
constructor(fragmentIdComparer) { private _pendingRedactions: Array<PendingEventEntry> | null
private _pendingAnnotations: Map<string, PendingAnnotation> | null
abstract id: string
abstract relatedEventId: string
abstract eventType: string
abstract content: any
constructor(fragmentIdComparer: FragmentIdComparer) {
super(fragmentIdComparer); super(fragmentIdComparer);
this._pendingRedactions = null; this._pendingRedactions = null;
this._pendingAnnotations = null; this._pendingAnnotations = null;
} }
get isRedacting() { get isRedacting(): boolean {
return !!this._pendingRedactions; return !!this._pendingRedactions;
} }
get isRedacted() { get isRedacted(): boolean {
return this.isRedacting; return this.isRedacting;
} }
get isRedaction() { get isRedaction(): boolean {
return this.eventType === REDACTION_TYPE; return this.eventType === REDACTION_TYPE;
} }
get redactionReason() { get redactionReason(): string | null {
if (this._pendingRedactions) { if (this._pendingRedactions) {
return this._pendingRedactions[0].content?.reason; return this._pendingRedactions[0].content?.reason;
} }
@ -51,7 +66,7 @@ export class BaseEventEntry extends BaseEntry {
aggregates local relation or local redaction of remote relation. aggregates local relation or local redaction of remote relation.
@return [string] returns the name of the field that has changed, if any @return [string] returns the name of the field that has changed, if any
*/ */
addLocalRelation(entry) { addLocalRelation(entry: PendingEventEntry): string | undefined {
if (entry.eventType === REDACTION_TYPE && entry.isRelatedToId(this.id)) { if (entry.eventType === REDACTION_TYPE && entry.isRelatedToId(this.id)) {
if (!this._pendingRedactions) { if (!this._pendingRedactions) {
this._pendingRedactions = []; this._pendingRedactions = [];
@ -76,7 +91,7 @@ export class BaseEventEntry extends BaseEntry {
deaggregates local relation or a local redaction of a remote relation. deaggregates local relation or a local redaction of a remote relation.
@return [string] returns the name of the field that has changed, if any @return [string] returns the name of the field that has changed, if any
*/ */
removeLocalRelation(entry) { removeLocalRelation(entry: PendingEventEntry): string | undefined {
if (entry.eventType === REDACTION_TYPE && entry.isRelatedToId(this.id) && this._pendingRedactions) { if (entry.eventType === REDACTION_TYPE && entry.isRelatedToId(this.id) && this._pendingRedactions) {
const countBefore = this._pendingRedactions.length; const countBefore = this._pendingRedactions.length;
this._pendingRedactions = this._pendingRedactions.filter(e => e !== entry); this._pendingRedactions = this._pendingRedactions.filter(e => e !== entry);
@ -98,7 +113,7 @@ export class BaseEventEntry extends BaseEntry {
} }
} }
_addPendingAnnotation(entry) { _addPendingAnnotation(entry: PendingEventEntry): boolean {
if (!this._pendingAnnotations) { if (!this._pendingAnnotations) {
this._pendingAnnotations = new Map(); this._pendingAnnotations = new Map();
} }
@ -115,14 +130,14 @@ export class BaseEventEntry extends BaseEntry {
return false; return false;
} }
_removePendingAnnotation(entry) { _removePendingAnnotation(entry: PendingEventEntry) {
const {key} = (entry.redactingEntry || entry).relation; const {key} = (entry.redactingEntry || entry).relation;
if (key) { if (key) {
let annotation = this._pendingAnnotations.get(key); let annotation = this._pendingAnnotations?.get(key);
if (annotation.remove(entry) && annotation.isEmpty) { if (annotation.remove(entry) && annotation.isEmpty) {
this._pendingAnnotations.delete(key); this._pendingAnnotations?.delete(key);
} }
if (this._pendingAnnotations.size === 0) { if (this._pendingAnnotations?.size === 0) {
this._pendingAnnotations = null; this._pendingAnnotations = null;
} }
return true; return true;
@ -140,23 +155,23 @@ export class BaseEventEntry extends BaseEntry {
} }
} }
get pendingRedaction() { get pendingRedaction(): PendingEventEntry | null {
if (this._pendingRedactions) { if (this._pendingRedactions) {
return this._pendingRedactions[0]; return this._pendingRedactions[0];
} }
return null; return null;
} }
annotate(key) { annotate(key: string): any {
return createAnnotation(this.id, key); return createAnnotation(this.id, key);
} }
/** takes both remote event id and local txn id into account, see overriding in PendingEventEntry */ /** takes both remote event id and local txn id into account, see overriding in PendingEventEntry */
isRelatedToId(id) { isRelatedToId(id: string | null): boolean {
return id && this.relatedEventId === id; return !!id && this.relatedEventId === id;
} }
haveAnnotation(key) { haveAnnotation(key: string): boolean {
const haveRemoteReaction = this.annotations?.[key]?.me || false; const haveRemoteReaction = this.annotations?.[key]?.me || false;
const pendingAnnotation = this.pendingAnnotations?.get(key); const pendingAnnotation = this.pendingAnnotations?.get(key);
const willAnnotate = pendingAnnotation?.willAnnotate || false; const willAnnotate = pendingAnnotation?.willAnnotate || false;
@ -170,15 +185,15 @@ export class BaseEventEntry extends BaseEntry {
(!haveRemoteReaction && willAnnotate); (!haveRemoteReaction && willAnnotate);
} }
get relation() { get relation(): any {
return getRelationFromContent(this.content); return getRelationFromContent(this.content);
} }
get pendingAnnotations() { get pendingAnnotations(): Map<string, PendingAnnotation> | null {
return this._pendingAnnotations; return this._pendingAnnotations;
} }
get annotations() { get annotations(): { [key: string]: Annotation } | null {
return null; //overwritten in EventEntry return null; //overwritten in EventEntry
} }
} }

View file

@ -14,25 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {FragmentIdComparer} from "./BaseEntry.js"
import {BaseEventEntry} from "./BaseEventEntry.js"; import {BaseEventEntry} from "./BaseEventEntry.js";
import {getPrevContentFromStateEvent, isRedacted} from "../../common.js"; import {getPrevContentFromStateEvent, isRedacted} from "../../common.js";
import {getRelatedEventId} from "../relations.js"; import {getRelatedEventId} from "../relations.js";
export class EventEntry extends BaseEventEntry { export class EventEntry extends BaseEventEntry {
constructor(eventEntry, fragmentIdComparer) { private _eventEntry: any // TODO Need type
private _decryptionError: Error | null
private _decryptionResult: any | null // TODO Need type
constructor(eventEntry: any, fragmentIdComparer: FragmentIdComparer = { compare: (x, y) => 0 }) {
super(fragmentIdComparer); super(fragmentIdComparer);
this._eventEntry = eventEntry; this._eventEntry = eventEntry;
this._decryptionError = null; this._decryptionError = null;
this._decryptionResult = null; this._decryptionResult = null;
} }
clone() { clone(): EventEntry {
const clone = new EventEntry(this._eventEntry, this._fragmentIdComparer); const clone = new EventEntry(this._eventEntry, this._fragmentIdComparer);
clone.updateFrom(this); clone.updateFrom(this);
return clone; return clone;
} }
updateFrom(other) { updateFrom(other: EventEntry) {
super.updateFrom(other); super.updateFrom(other);
if (other._decryptionResult && !this._decryptionResult) { if (other._decryptionResult && !this._decryptionResult) {
this._decryptionResult = other._decryptionResult; this._decryptionResult = other._decryptionResult;

View file

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js"; import {PENDING_FRAGMENT_ID} from "./BaseEntry";
import {BaseEventEntry} from "./BaseEventEntry.js"; import {BaseEventEntry} from "./BaseEventEntry";
export class PendingEventEntry extends BaseEventEntry { export class PendingEventEntry extends BaseEventEntry {
constructor({pendingEvent, member, clock, redactingEntry}) { constructor({pendingEvent, member, clock, redactingEntry}) {

View file

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js"; import fdb from "fake-indexeddb";
import {StorageFactory} from "../matrix/storage/idb/StorageFactory.js"; import {StorageFactory} from "../matrix/storage/idb/StorageFactory.js";
export function createMockStorage() { export function createMockStorage() {
return new StorageFactory(null, new FDBFactory(), FDBKeyRange).create(1); return new StorageFactory(null, new fdb.FDBFactory(), fdb.FDBKeyRange).create(1);
} }

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import aesjs from "../../../lib/aes-js/index.js"; import aesjs from "aes-js";
import {hkdf} from "../../utils/crypto/hkdf.js"; import {hkdf} from "../../utils/crypto/hkdf.js";
import {Platform as ModernPlatform} from "./Platform.js"; import {Platform as ModernPlatform} from "./Platform.js";

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../../lib/base64-arraybuffer/index.js"; import base64 from "base64-arraybuffer";
// turn IE11 result into promise // turn IE11 result into promise
function subtleCryptoResult(promiseOrOp, method) { function subtleCryptoResult(promiseOrOp, method) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import DOMPurify from "../../../lib/dompurify/index.js" import DOMPurify from "dompurify"
class HTMLParseResult { class HTMLParseResult {
constructor(bodyNode) { constructor(bodyNode) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import base64 from "../../../../lib/base64-arraybuffer/index.js"; import base64 from "base64-arraybuffer";
export class Base64 { export class Base64 {
encodeUnpadded(buffer) { encodeUnpadded(buffer) {

2367
yarn.lock

File diff suppressed because it is too large Load diff