diff --git a/index.html b/index.html
index f2d7a261..af5f513b 100644
--- a/index.html
+++ b/index.html
@@ -1,7 +1,7 @@
-
-
+
+
@@ -9,25 +9,26 @@
-
-
-
-
-
+
+
+
+
+
-
-
+ }));
+
+
diff --git a/package.json b/package.json
index 08ad3d19..478b621b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "hydrogen-web",
- "version": "0.1.19",
+ "version": "0.1.20",
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
"main": "index.js",
"directories": {
diff --git a/scripts/build.mjs b/scripts/build.mjs
index 1534fc7a..718802d1 100644
--- a/scripts/build.mjs
+++ b/scripts/build.mjs
@@ -45,12 +45,12 @@ import flexbugsFixes from "postcss-flexbugs-fixes";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectDir = path.join(__dirname, "../");
-const cssSrcDir = path.join(projectDir, "src/ui/web/css/");
+const cssSrcDir = path.join(projectDir, "src/platform/web/ui/css/");
-const program = new commander.Command();
-program
+const parameters = new commander.Command();
+parameters
.option("--modern-only", "don't make a legacy build")
-program.parse(process.argv);
+parameters.parse(process.argv);
async function build({modernOnly}) {
// get version number
@@ -70,10 +70,13 @@ async function build({modernOnly}) {
// copy olm assets
const olmAssets = await copyFolder(path.join(projectDir, "lib/olm/"), assets.directory);
assets.addSubMap(olmAssets);
- await assets.write(`hydrogen.js`, await buildJs("src/main.js"));
+ await assets.write(`hydrogen.js`, await buildJs("src/main.js", ["src/platform/web/Platform.js"]));
if (!modernOnly) {
- await assets.write(`hydrogen-legacy.js`, await buildJsLegacy("src/main.js", ['src/legacy-polyfill.js', 'src/legacy-extras.js']));
- await assets.write(`worker.js`, await buildJsLegacy("src/worker.js", ['src/worker-polyfill.js']));
+ await assets.write(`hydrogen-legacy.js`, await buildJsLegacy("src/main.js", [
+ 'src/platform/web/legacy-polyfill.js',
+ 'src/platform/web/LegacyPlatform.js'
+ ]));
+ await assets.write(`worker.js`, await buildJsLegacy("src/platform/web/worker/main.js", ['src/platform/web/worker/polyfill.js']));
}
// creates the directories where the theme css bundles are placed in,
// and writes to assets, so the build bundles can translate them, so do it first
@@ -82,7 +85,7 @@ async function build({modernOnly}) {
await buildManifest(assets);
// all assets have been added, create a hash from all assets name to cache unhashed files like index.html
assets.addToHashForAll("index.html", devHtml);
- let swSource = await fs.readFile(path.join(projectDir, "src/service-worker.template.js"), "utf8");
+ let swSource = await fs.readFile(path.join(projectDir, "src/platform/web/service-worker.template.js"), "utf8");
assets.addToHashForAll("sw.js", swSource);
const globalHash = assets.hashForAll();
@@ -148,12 +151,12 @@ async function buildHtml(doc, version, globalHash, modernOnly, assets) {
}
});
const mainScripts = [
- ``
+ ``
];
if (!modernOnly) {
mainScripts.push(
``,
- ``
+ ``
);
}
doc("script#main").replaceWith(mainScripts.join(""));
@@ -168,16 +171,16 @@ async function buildHtml(doc, version, globalHash, modernOnly, assets) {
await assets.writeUnhashed("index.html", doc.html());
}
-async function buildJs(inputFile) {
+async function buildJs(mainFile, extraFiles = []) {
// create js bundle
const bundle = await rollup({
- input: inputFile,
- plugins: [removeJsComments({comments: "none"})]
+ input: extraFiles.concat(mainFile),
+ plugins: [multi(), removeJsComments({comments: "none"})]
});
const {output} = await bundle.generate({
format: 'es',
// TODO: can remove this?
- name: `hydrogenBundle`
+ name: `hydrogen`
});
const code = output[0].code;
return code;
@@ -214,7 +217,7 @@ async function buildJsLegacy(mainFile, extraFiles = []) {
const bundle = await rollup(rollupConfig);
const {output} = await bundle.generate({
format: 'iife',
- name: `hydrogenBundle`
+ name: `hydrogen`
});
const code = output[0].code;
return code;
@@ -460,4 +463,4 @@ class AssetMap {
}
}
-build(program).catch(err => console.error(err));
+build(parameters).catch(err => console.error(err));
diff --git a/src/Platform.js b/src/Platform.js
deleted file mode 100644
index 48a1b920..00000000
--- a/src/Platform.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
-Copyright 2020 Bruno Windels
-
-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";
diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js
index 6669b778..cb22bee2 100644
--- a/src/domain/RootViewModel.js
+++ b/src/domain/RootViewModel.js
@@ -23,10 +23,7 @@ import {ViewModel} from "./ViewModel.js";
export class RootViewModel extends ViewModel {
constructor(options) {
super(options);
- const {createSessionContainer, sessionInfoStorage, storageFactory} = options;
- this._createSessionContainer = createSessionContainer;
- this._sessionInfoStorage = sessionInfoStorage;
- this._storageFactory = storageFactory;
+ this._createSessionContainer = options.createSessionContainer;
this._error = null;
this._sessionPickerViewModel = null;
this._sessionLoadViewModel = null;
@@ -73,7 +70,7 @@ export class RootViewModel extends ViewModel {
if (restoreUrlIfAtDefault) {
this.urlCreator.pushUrl(restoreUrlIfAtDefault);
} else {
- const sessionInfos = await this._sessionInfoStorage.getAll();
+ const sessionInfos = await this.platform.sessionInfoStorage.getAll();
if (sessionInfos.length === 0) {
this.navigation.push("login");
} else if (sessionInfos.length === 1) {
@@ -90,10 +87,7 @@ export class RootViewModel extends ViewModel {
async _showPicker() {
this._setSection(() => {
- this._sessionPickerViewModel = new SessionPickerViewModel(this.childOptions({
- sessionInfoStorage: this._sessionInfoStorage,
- storageFactory: this._storageFactory,
- }));
+ this._sessionPickerViewModel = new SessionPickerViewModel(this.childOptions());
});
try {
await this._sessionPickerViewModel.load();
@@ -125,11 +119,7 @@ export class RootViewModel extends ViewModel {
_showSession(sessionContainer) {
this._setSection(() => {
- this._sessionViewModel = new SessionViewModel(this.childOptions({
- sessionContainer,
- updateService: this.getOption("updateService"),
- estimateStorageUsage: this.getOption("estimateStorageUsage"),
- }));
+ this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer}));
this._sessionViewModel.start();
});
}
diff --git a/src/domain/SessionPickerViewModel.js b/src/domain/SessionPickerViewModel.js
index 76be11dd..3bbbcef7 100644
--- a/src/domain/SessionPickerViewModel.js
+++ b/src/domain/SessionPickerViewModel.js
@@ -130,9 +130,6 @@ class SessionItemViewModel extends ViewModel {
export class SessionPickerViewModel extends ViewModel {
constructor(options) {
super(options);
- const {storageFactory, sessionInfoStorage} = options;
- this._storageFactory = storageFactory;
- this._sessionInfoStorage = sessionInfoStorage;
this._sessions = new SortedArray((s1, s2) => s1.id.localeCompare(s2.id));
this._loadViewModel = null;
this._error = null;
@@ -140,7 +137,7 @@ export class SessionPickerViewModel extends ViewModel {
// this loads all the sessions
async load() {
- const sessions = await this._sessionInfoStorage.getAll();
+ const sessions = await this.platform.sessionInfoStorage.getAll();
this._sessions.setManyUnsorted(sessions.map(s => {
return new SessionItemViewModel(this.childOptions({sessionInfo: s}), this);
}));
@@ -152,8 +149,8 @@ export class SessionPickerViewModel extends ViewModel {
}
async _exportData(id) {
- const sessionInfo = await this._sessionInfoStorage.get(id);
- const stores = await this._storageFactory.export(id);
+ const sessionInfo = await this.platform.sessionInfoStorage.get(id);
+ const stores = await this.platform.storageFactory.export(id);
const data = {sessionInfo, stores};
return data;
}
@@ -164,8 +161,8 @@ export class SessionPickerViewModel extends ViewModel {
const {sessionInfo} = data;
sessionInfo.comment = `Imported on ${new Date().toLocaleString()} from id ${sessionInfo.id}.`;
sessionInfo.id = this._createSessionContainer().createNewSessionId();
- await this._storageFactory.import(sessionInfo.id, data.stores);
- await this._sessionInfoStorage.add(sessionInfo);
+ await this.platform.storageFactory.import(sessionInfo.id, data.stores);
+ await this.platform.sessionInfoStorage.add(sessionInfo);
this._sessions.set(new SessionItemViewModel(sessionInfo, this));
} catch (err) {
alert(err.message);
@@ -175,13 +172,13 @@ export class SessionPickerViewModel extends ViewModel {
async delete(id) {
const idx = this._sessions.array.findIndex(s => s.id === id);
- await this._sessionInfoStorage.delete(id);
- await this._storageFactory.delete(id);
+ await this.platform.sessionInfoStorage.delete(id);
+ await this.platform.storageFactory.delete(id);
this._sessions.remove(idx);
}
async clear(id) {
- await this._storageFactory.delete(id);
+ await this.platform.storageFactory.delete(id);
}
get sessions() {
diff --git a/src/domain/ViewModel.js b/src/domain/ViewModel.js
index a8b58921..dd2d9819 100644
--- a/src/domain/ViewModel.js
+++ b/src/domain/ViewModel.js
@@ -30,8 +30,8 @@ export class ViewModel extends EventEmitter {
}
childOptions(explicitOptions) {
- const {navigation, urlCreator, clock} = this._options;
- return Object.assign({navigation, urlCreator, clock}, explicitOptions);
+ const {navigation, urlCreator, platform} = this._options;
+ return Object.assign({navigation, urlCreator, platform}, explicitOptions);
}
// makes it easier to pass through dependencies of a sub-view model
@@ -100,8 +100,12 @@ export class ViewModel extends EventEmitter {
}
}
+ get platform() {
+ return this._options.platform;
+ }
+
get clock() {
- return this._options.clock;
+ return this._options.platform.clock;
}
/**
diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js
index ed779747..79d8d87c 100644
--- a/src/domain/session/SessionViewModel.js
+++ b/src/domain/session/SessionViewModel.js
@@ -188,9 +188,7 @@ export class SessionViewModel extends ViewModel {
}
if (settingsOpen) {
this._settingsViewModel = this.track(new SettingsViewModel(this.childOptions({
- updateService: this.getOption("updateService"),
session: this._sessionContainer.session,
- estimateStorageUsage: this.getOption("estimateStorageUsage")
})));
this._settingsViewModel.load();
}
diff --git a/src/domain/session/room/timeline/TimelineViewModel.js b/src/domain/session/room/timeline/TimelineViewModel.js
index 5faa23b3..7366641b 100644
--- a/src/domain/session/room/timeline/TimelineViewModel.js
+++ b/src/domain/session/room/timeline/TimelineViewModel.js
@@ -43,7 +43,7 @@ export class TimelineViewModel extends ViewModel {
// 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, clock: this.clock}));
+ this._tiles = new TilesCollection(timeline.entries, tilesCreator({room, ownUserId, platform: this.platform}));
}
async load() {
diff --git a/src/domain/session/room/timeline/tiles/MessageTile.js b/src/domain/session/room/timeline/tiles/MessageTile.js
index 9db96eff..36d08ca7 100644
--- a/src/domain/session/room/timeline/tiles/MessageTile.js
+++ b/src/domain/session/room/timeline/tiles/MessageTile.js
@@ -21,7 +21,6 @@ export class MessageTile extends SimpleTile {
constructor(options) {
super(options);
this._mediaRepository = options.mediaRepository;
- this._clock = options.clock;
this._isOwn = this._entry.sender === options.ownUserId;
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
this._isContinuation = false;
@@ -88,8 +87,8 @@ export class MessageTile extends SimpleTile {
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();
+ 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);
}
diff --git a/src/domain/session/room/timeline/tilesCreator.js b/src/domain/session/room/timeline/tilesCreator.js
index 5f5593d5..d682d22e 100644
--- a/src/domain/session/room/timeline/tilesCreator.js
+++ b/src/domain/session/room/timeline/tilesCreator.js
@@ -23,9 +23,9 @@ import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
import {EncryptedEventTile} from "./tiles/EncryptedEventTile.js";
import {EncryptionEnabledTile} from "./tiles/EncryptionEnabledTile.js";
-export function tilesCreator({room, ownUserId, clock}) {
+export function tilesCreator({room, ownUserId, platform}) {
return function tilesCreator(entry, emitUpdate) {
- const options = {entry, emitUpdate, ownUserId, clock,
+ const options = {entry, emitUpdate, ownUserId, platform,
mediaRepository: room.mediaRepository};
if (entry.isGap) {
return new GapTile(options, room);
diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js
index fce74294..0a1f223c 100644
--- a/src/domain/session/settings/SettingsViewModel.js
+++ b/src/domain/session/settings/SettingsViewModel.js
@@ -35,12 +35,11 @@ export class SettingsViewModel extends ViewModel {
this._session = session;
this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session})));
this._closeUrl = this.urlCreator.urlUntilSegment("session");
- this._estimateStorageUsage = options.estimateStorageUsage;
this._estimate = null;
}
async load() {
- this._estimate = await this._estimateStorageUsage();
+ this._estimate = await this.platform.estimateStorageUsage();
this.emitChange("");
}
@@ -61,18 +60,19 @@ export class SettingsViewModel extends ViewModel {
}
get version() {
- if (this._updateService) {
- return `${this._updateService.version} (${this._updateService.buildHash})`;
+ const {updateService} = this.platform;
+ if (updateService) {
+ return `${updateService.version} (${updateService.buildHash})`;
}
return this.i18n`development version`;
}
checkForUpdate() {
- this._updateService?.checkForUpdate();
+ this.platform.updateService?.checkForUpdate();
}
get showUpdateButton() {
- return !!this._updateService;
+ return !!this.platform.updateService;
}
get sessionBackupViewModel() {
diff --git a/src/legacy-extras.js b/src/legacy-extras.js
deleted file mode 100644
index e6b1f08e..00000000
--- a/src/legacy-extras.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import aesjs from "../lib/aes-js/index.js";
-import {hkdf} from "./utils/crypto/hkdf.js";
-
-// these are run-time dependencies that are only needed for the legacy bundle.
-// they are exported here and passed into main to make them available to the app.
-export const legacyExtras = {crypto:{aesjs, hkdf}};
diff --git a/src/main.js b/src/main.js
index 1eb29c4c..0754b2ab 100644
--- a/src/main.js
+++ b/src/main.js
@@ -16,82 +16,14 @@ limitations under the License.
*/
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay.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";
import {RootViewModel} from "./domain/RootViewModel.js";
import {createNavigation, createRouter} from "./domain/navigation/index.js";
-import {RootView} from "./ui/web/RootView.js";
-import {Clock} from "./ui/web/dom/Clock.js";
-import {ServiceWorkerHandler} from "./ui/web/dom/ServiceWorkerHandler.js";
-import {History} from "./ui/web/dom/History.js";
-import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
-import {CryptoDriver} from "./ui/web/dom/CryptoDriver.js";
-import {estimateStorageUsage} from "./ui/web/dom/StorageEstimate.js";
-import {WorkerPool} from "./utils/WorkerPool.js";
-import {OlmWorker} from "./matrix/e2ee/OlmWorker.js";
-
-function addScript(src) {
- return new Promise(function (resolve, reject) {
- var s = document.createElement("script");
- s.setAttribute("src", src );
- s.onload=resolve;
- s.onerror=reject;
- document.body.appendChild(s);
- });
-}
-
-async function loadOlm(olmPaths) {
- // make crypto.getRandomValues available without
- // a prefix on IE11, needed by olm to work
- if (window.msCrypto && !window.crypto) {
- window.crypto = window.msCrypto;
- }
- if (olmPaths) {
- if (window.WebAssembly) {
- await addScript(olmPaths.wasmBundle);
- await window.Olm.init({locateFile: () => olmPaths.wasm});
- } else {
- await addScript(olmPaths.legacyBundle);
- await window.Olm.init();
- }
- return window.Olm;
- }
- return null;
-}
-
-// make path relative to basePath,
-// assuming it and basePath are relative to document
-function relPath(path, basePath) {
- const idx = basePath.lastIndexOf("/");
- const dir = idx === -1 ? "" : basePath.slice(0, idx);
- const dirCount = dir.length ? dir.split("/").length : 0;
- return "../".repeat(dirCount) + path;
-}
-
-async function loadOlmWorker(paths) {
- const workerPool = new WorkerPool(paths.worker, 4);
- await workerPool.init();
- const path = relPath(paths.olm.legacyBundle, paths.worker);
- await workerPool.sendAll({type: "load_olm", path});
- const olmWorker = new OlmWorker(workerPool);
- return olmWorker;
-}
-
// 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, paths, legacyExtras) {
+export async function main(platform) {
try {
- // TODO: add .legacy to .hydrogen (container) in (legacy)platform.createAndMountRootView; and use .hydrogen:not(.legacy) if needed for modern stuff
- const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
- if (isIE11) {
- document.body.className += " ie11";
- } else {
- document.body.className += " not-ie11";
- }
// to replay:
// const fetchLog = await (await fetch("/fetchlogs/constrainterror.json")).json();
// const replay = new ReplayRequester(fetchLog, {delay: false});
@@ -101,61 +33,25 @@ export async function main(container, paths, legacyExtras) {
// const recorder = new RecordRequester(createFetchRequest(clock.createTimeout));
// const request = recorder.request;
// window.getBrawlFetchLog = () => recorder.log();
- const clock = new Clock();
- let request;
- if (typeof fetch === "function") {
- request = createFetchRequest(clock.createTimeout);
- } else {
- request = xhrRequest;
- }
const navigation = createNavigation();
- const sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1");
- let serviceWorkerHandler;
- if (paths.serviceWorker && "serviceWorker" in navigator) {
- serviceWorkerHandler = new ServiceWorkerHandler({navigation});
- serviceWorkerHandler.registerAndStart(paths.serviceWorker);
- }
- const storageFactory = new StorageFactory(serviceWorkerHandler);
-
- const olmPromise = loadOlm(paths.olm);
- // if wasm is not supported, we'll want
- // to run some olm operations in a worker (mainly for IE11)
- let workerPromise;
- if (!window.WebAssembly) {
- workerPromise = loadOlmWorker(paths);
- }
- const urlRouter = createRouter({navigation, history: new History()});
+ platform.setNavigation(navigation);
+ const urlRouter = createRouter({navigation, history: platform.history});
urlRouter.attach();
+ const olmPromise = platform.loadOlm();
+ const workerPromise = platform.loadOlmWorker();
const vm = new RootViewModel({
createSessionContainer: () => {
- return new SessionContainer({
- random: Math.random,
- onlineStatus: new OnlineStatus(),
- storageFactory,
- sessionInfoStorage,
- request,
- clock,
- cryptoDriver: new CryptoDriver(legacyExtras?.crypto),
- olmPromise,
- workerPromise,
- });
+ return new SessionContainer({platform, olmPromise, workerPromise});
},
- sessionInfoStorage,
- storageFactory,
- clock,
+ platform,
// the only public interface of the router is to create urls,
// so we call it that in the view models
urlCreator: urlRouter,
navigation,
- updateService: serviceWorkerHandler,
- estimateStorageUsage
});
- window.__hydrogenViewModel = vm;
await vm.load();
- // TODO: replace with platform.createAndMountRootView(vm, container);
- const view = new RootView(vm);
- container.appendChild(view.mount());
+ platform.createAndMountRootView(vm);
} catch(err) {
console.error(`${err.message}:\n${err.stack}`);
}
diff --git a/src/matrix/Session.js b/src/matrix/Session.js
index e0ee215e..15487ec5 100644
--- a/src/matrix/Session.js
+++ b/src/matrix/Session.js
@@ -41,8 +41,8 @@ const PICKLE_KEY = "DEFAULT_KEY";
export class Session {
// sessionInfo contains deviceId, userId and homeServer
- constructor({clock, storage, hsApi, sessionInfo, olm, olmWorker, cryptoDriver, mediaRepository}) {
- this._clock = clock;
+ constructor({storage, hsApi, sessionInfo, olm, olmWorker, platform, mediaRepository}) {
+ this._platform = platform;
this._storage = storage;
this._hsApi = hsApi;
this._mediaRepository = mediaRepository;
@@ -61,7 +61,6 @@ export class Session {
this._megolmDecryption = null;
this._getSyncToken = () => this.syncToken;
this._olmWorker = olmWorker;
- this._cryptoDriver = cryptoDriver;
this._sessionBackup = null;
this._hasSecretStorageKey = new ObservableValue(null);
@@ -106,7 +105,7 @@ export class Session {
pickleKey: PICKLE_KEY,
olm: this._olm,
storage: this._storage,
- now: this._clock.now,
+ now: this._platform.clock.now,
ownUserId: this._user.id,
senderKeyLock
});
@@ -115,7 +114,7 @@ export class Session {
pickleKey: PICKLE_KEY,
olm: this._olm,
storage: this._storage,
- now: this._clock.now,
+ now: this._platform.clock.now,
ownUserId: this._user.id,
olmUtil: this._olmUtil,
senderKeyLock
@@ -125,7 +124,7 @@ export class Session {
pickleKey: PICKLE_KEY,
olm: this._olm,
storage: this._storage,
- now: this._clock.now,
+ now: this._platform.clock.now,
ownDeviceId: this._sessionInfo.deviceId,
});
this._megolmDecryption = new MegOlmDecryption({
@@ -166,7 +165,7 @@ export class Session {
this.needsSessionBackup.set(true)
}
},
- clock: this._clock
+ clock: this._platform.clock
});
}
@@ -185,7 +184,7 @@ export class Session {
if (this._sessionBackup) {
return false;
}
- const key = await ssssKeyFromCredential(type, credential, this._storage, this._cryptoDriver, this._olm);
+ const key = await ssssKeyFromCredential(type, credential, this._storage, this._platform.crypto, this._olm);
// and create session backup, which needs to read from accountData
const readTxn = this._storage.readTxn([
this._storage.storeNames.accountData,
@@ -207,7 +206,7 @@ export class Session {
}
async _createSessionBackup(ssssKey, txn) {
- const secretStorage = new SecretStorage({key: ssssKey, cryptoDriver: this._cryptoDriver});
+ const secretStorage = new SecretStorage({key: ssssKey, crypto: this._platform.crypto});
this._sessionBackup = await SessionBackup.fromSecretStorage({olm: this._olm, secretStorage, hsApi: this._hsApi, txn});
if (this._sessionBackup) {
for (const room of this._rooms.values()) {
@@ -363,7 +362,7 @@ export class Session {
pendingEvents,
user: this._user,
createRoomEncryption: this._createRoomEncryption,
- clock: this._clock
+ clock: this._platform.clock
});
this._rooms.add(roomId, room);
return room;
diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js
index fdcd0a96..e0fcf951 100644
--- a/src/matrix/SessionContainer.js
+++ b/src/matrix/SessionContainer.js
@@ -44,13 +44,8 @@ export const LoginFailure = createEnum(
);
export class SessionContainer {
- constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage, olmPromise, workerPromise, cryptoDriver}) {
- this._random = random;
- this._clock = clock;
- this._onlineStatus = onlineStatus;
- this._request = request;
- this._storageFactory = storageFactory;
- this._sessionInfoStorage = sessionInfoStorage;
+ constructor({platform, olmPromise, workerPromise}) {
+ this._platform = platform;
this._sessionStartedByReconnector = false;
this._status = new ObservableValue(LoadStatus.NotLoading);
this._error = null;
@@ -63,11 +58,10 @@ export class SessionContainer {
this._requestScheduler = null;
this._olmPromise = olmPromise;
this._workerPromise = workerPromise;
- this._cryptoDriver = cryptoDriver;
}
createNewSessionId() {
- return (Math.floor(this._random() * Number.MAX_SAFE_INTEGER)).toString();
+ return (Math.floor(this._platform.random() * Number.MAX_SAFE_INTEGER)).toString();
}
get sessionId() {
@@ -80,7 +74,7 @@ export class SessionContainer {
}
this._status.set(LoadStatus.Loading);
try {
- const sessionInfo = await this._sessionInfoStorage.get(sessionId);
+ const sessionInfo = await this._platform.sessionInfoStorage.get(sessionId);
if (!sessionInfo) {
throw new Error("Invalid session id: " + sessionId);
}
@@ -96,9 +90,11 @@ export class SessionContainer {
return;
}
this._status.set(LoadStatus.Login);
+ const clock = this._platform.clock;
let sessionInfo;
try {
- const hsApi = new HomeServerApi({homeServer, request: this._request, createTimeout: this._clock.createTimeout});
+ const request = this._platform.request;
+ const hsApi = new HomeServerApi({homeServer, request, createTimeout: clock.createTimeout});
const loginData = await hsApi.passwordLogin(username, password, "Hydrogen").response();
const sessionId = this.createNewSessionId();
sessionInfo = {
@@ -107,9 +103,9 @@ export class SessionContainer {
userId: loginData.user_id,
homeServer: homeServer,
accessToken: loginData.access_token,
- lastUsed: this._clock.now()
+ lastUsed: clock.now()
};
- await this._sessionInfoStorage.add(sessionInfo);
+ await this._platform.sessionInfoStorage.add(sessionInfo);
} catch (err) {
this._error = err;
if (err instanceof HomeServerError) {
@@ -139,22 +135,23 @@ export class SessionContainer {
}
async _loadSessionInfo(sessionInfo, isNewLogin) {
+ const clock = this._platform.clock;
this._sessionStartedByReconnector = false;
this._status.set(LoadStatus.Loading);
this._reconnector = new Reconnector({
- onlineStatus: this._onlineStatus,
- retryDelay: new ExponentialRetryDelay(this._clock.createTimeout),
- createMeasure: this._clock.createMeasure
+ onlineStatus: this._platform.onlineStatus,
+ retryDelay: new ExponentialRetryDelay(clock.createTimeout),
+ createMeasure: clock.createMeasure
});
const hsApi = new HomeServerApi({
homeServer: sessionInfo.homeServer,
accessToken: sessionInfo.accessToken,
- request: this._request,
+ request: this._platform.request,
reconnector: this._reconnector,
- createTimeout: this._clock.createTimeout
+ createTimeout: clock.createTimeout
});
this._sessionId = sessionInfo.id;
- this._storage = await this._storageFactory.create(sessionInfo.id);
+ this._storage = await this._platform.storageFactory.create(sessionInfo.id);
// no need to pass access token to session
const filteredSessionInfo = {
deviceId: sessionInfo.deviceId,
@@ -166,22 +163,21 @@ export class SessionContainer {
if (this._workerPromise) {
olmWorker = await this._workerPromise;
}
- this._requestScheduler = new RequestScheduler({hsApi, clock: this._clock});
+ this._requestScheduler = new RequestScheduler({hsApi, clock});
this._requestScheduler.start();
const mediaRepository = new MediaRepository({
homeServer: sessionInfo.homeServer,
- cryptoDriver: this._cryptoDriver,
- request: this._request,
+ crypto: this._platform.crypto,
+ request: this._platform.request,
});
this._session = new Session({
storage: this._storage,
sessionInfo: filteredSessionInfo,
hsApi: this._requestScheduler.hsApi,
olm,
- clock: this._clock,
olmWorker,
- cryptoDriver: this._cryptoDriver,
- mediaRepository
+ mediaRepository,
+ platform: this._platform,
});
await this._session.load();
if (isNewLogin) {
@@ -298,8 +294,8 @@ export class SessionContainer {
// if one fails, don't block the other from trying
// also, run in parallel
await Promise.all([
- this._storageFactory.delete(this._sessionId),
- this._sessionInfoStorage.delete(this._sessionId),
+ this._platform.storageFactory.delete(this._sessionId),
+ this._platform.sessionInfoStorage.delete(this._sessionId),
]);
this._sessionId = null;
}
diff --git a/src/matrix/e2ee/attachment.js b/src/matrix/e2ee/attachment.js
index 26560ca6..408e04fe 100644
--- a/src/matrix/e2ee/attachment.js
+++ b/src/matrix/e2ee/attachment.js
@@ -25,7 +25,7 @@ import base64 from "../../../lib/base64-arraybuffer/index.js";
* @param {string} info.hashes.sha256 Base64 encoded SHA-256 hash of the ciphertext.
* @return {Promise} A promise that resolves with an ArrayBuffer when the attachment is decrypted.
*/
-export async function decryptAttachment(cryptoDriver, ciphertextBuffer, info) {
+export async function decryptAttachment(crypto, ciphertextBuffer, info) {
if (info === undefined || info.key === undefined || info.iv === undefined
|| info.hashes === undefined || info.hashes.sha256 === undefined) {
throw new Error("Invalid info. Missing info.key, info.iv or info.hashes.sha256 key");
@@ -35,7 +35,7 @@ export async function decryptAttachment(cryptoDriver, ciphertextBuffer, info) {
// re-encode to not deal with padded vs unpadded
var expectedSha256base64 = base64.encode(base64.decode(info.hashes.sha256));
// Check the sha256 hash
- const digestResult = await cryptoDriver.digest("SHA-256", ciphertextBuffer);
+ const digestResult = await crypto.digest("SHA-256", ciphertextBuffer);
if (base64.encode(new Uint8Array(digestResult)) != expectedSha256base64) {
throw new Error("Mismatched SHA-256 digest");
}
@@ -48,7 +48,7 @@ export async function decryptAttachment(cryptoDriver, ciphertextBuffer, info) {
counterLength = 128;
}
- const decryptedBuffer = await cryptoDriver.aes.decryptCTR({
+ const decryptedBuffer = await crypto.aes.decryptCTR({
jwkKey: info.key,
iv: ivArray,
data: ciphertextBuffer,
diff --git a/src/matrix/net/MediaRepository.js b/src/matrix/net/MediaRepository.js
index cc7721ab..ac953448 100644
--- a/src/matrix/net/MediaRepository.js
+++ b/src/matrix/net/MediaRepository.js
@@ -18,9 +18,9 @@ import {encodeQueryParams} from "./common.js";
import {decryptAttachment} from "../e2ee/attachment.js";
export class MediaRepository {
- constructor({homeServer, cryptoDriver, request}) {
+ constructor({homeServer, crypto, request}) {
this._homeServer = homeServer;
- this._cryptoDriver = cryptoDriver;
+ this._crypto = crypto;
this._request = request;
}
@@ -56,7 +56,7 @@ export class MediaRepository {
async downloadEncryptedFile(fileEntry) {
const url = this.mxcUrl(fileEntry.url);
const {body: encryptedBuffer} = await this._request(url, {format: "buffer", cache: true}).response();
- const decryptedBuffer = await decryptAttachment(this._cryptoDriver, encryptedBuffer, fileEntry);
+ const decryptedBuffer = await decryptAttachment(this._crypto, encryptedBuffer, fileEntry);
return decryptedBuffer;
}
}
diff --git a/src/matrix/net/common.js b/src/matrix/net/common.js
index 6a772463..c7a06351 100644
--- a/src/matrix/net/common.js
+++ b/src/matrix/net/common.js
@@ -26,25 +26,3 @@ export function encodeQueryParams(queryParams) {
})
.join("&");
}
-
-export 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 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");
- }
- }
-}
diff --git a/src/matrix/room/timeline/EventKey.js b/src/matrix/room/timeline/EventKey.js
index e1771258..11d625e6 100644
--- a/src/matrix/room/timeline/EventKey.js
+++ b/src/matrix/room/timeline/EventKey.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {Platform} from "../../../Platform.js";
+import {KeyLimits} from "../../storage/common.js";
// key for events in the timelineEvents store
export class EventKey {
@@ -25,7 +25,7 @@ export class EventKey {
nextFragmentKey() {
// could take MIN_EVENT_INDEX here if it can't be paged back
- return new EventKey(this.fragmentId + 1, Platform.middleStorageKey);
+ return new EventKey(this.fragmentId + 1, KeyLimits.middleStorageKey);
}
nextKeyForDirection(direction) {
@@ -45,19 +45,19 @@ export class EventKey {
}
static get maxKey() {
- return new EventKey(Platform.maxStorageKey, Platform.maxStorageKey);
+ return new EventKey(KeyLimits.maxStorageKey, KeyLimits.maxStorageKey);
}
static get minKey() {
- return new EventKey(Platform.minStorageKey, Platform.minStorageKey);
+ return new EventKey(KeyLimits.minStorageKey, KeyLimits.minStorageKey);
}
static get defaultLiveKey() {
- return EventKey.defaultFragmentKey(Platform.minStorageKey);
+ return EventKey.defaultFragmentKey(KeyLimits.minStorageKey);
}
static defaultFragmentKey(fragmentId) {
- return new EventKey(fragmentId, Platform.middleStorageKey);
+ return new EventKey(fragmentId, KeyLimits.middleStorageKey);
}
toString() {
diff --git a/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js b/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js
index 791c7a9f..f43f273b 100644
--- a/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js
+++ b/src/matrix/room/timeline/entries/FragmentBoundaryEntry.js
@@ -17,7 +17,7 @@ limitations under the License.
import {BaseEntry} from "./BaseEntry.js";
import {Direction} from "../Direction.js";
import {isValidFragmentId} from "../common.js";
-import {Platform} from "../../../../Platform.js";
+import {KeyLimits} from "../../../storage/common.js";
export class FragmentBoundaryEntry extends BaseEntry {
constructor(fragment, isFragmentStart, fragmentIdComparer) {
@@ -53,9 +53,9 @@ export class FragmentBoundaryEntry extends BaseEntry {
get entryIndex() {
if (this.started) {
- return Platform.minStorageKey;
+ return KeyLimits.minStorageKey;
} else {
- return Platform.maxStorageKey;
+ return KeyLimits.maxStorageKey;
}
}
diff --git a/src/matrix/ssss/SecretStorage.js b/src/matrix/ssss/SecretStorage.js
index fdea7187..ae71280d 100644
--- a/src/matrix/ssss/SecretStorage.js
+++ b/src/matrix/ssss/SecretStorage.js
@@ -17,9 +17,9 @@ limitations under the License.
import base64 from "../../../lib/base64-arraybuffer/index.js";
export class SecretStorage {
- constructor({key, cryptoDriver}) {
+ constructor({key, crypto}) {
this._key = key;
- this._cryptoDriver = cryptoDriver;
+ this._crypto = crypto;
}
async readSecret(name, txn) {
@@ -44,7 +44,7 @@ export class SecretStorage {
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
// now derive the aes and mac key from the 4s key
- const hkdfKey = await this._cryptoDriver.derive.hkdf(
+ const hkdfKey = await this._crypto.derive.hkdf(
this._key.binaryKey,
new Uint8Array(8).buffer, //zero salt
textEncoder.encode(type), // info
@@ -56,7 +56,7 @@ export class SecretStorage {
const ciphertextBytes = base64.decode(encryptedData.ciphertext);
- const isVerified = await this._cryptoDriver.hmac.verify(
+ const isVerified = await this._crypto.hmac.verify(
hmacKey, base64.decode(encryptedData.mac),
ciphertextBytes, "SHA-256");
@@ -64,7 +64,7 @@ export class SecretStorage {
throw new Error("Bad MAC");
}
- const plaintextBytes = await this._cryptoDriver.aes.decryptCTR({
+ const plaintextBytes = await this._crypto.aes.decryptCTR({
key: aesKey,
iv: base64.decode(encryptedData.iv),
data: ciphertextBytes
diff --git a/src/matrix/ssss/index.js b/src/matrix/ssss/index.js
index 25c85b8e..222c4b46 100644
--- a/src/matrix/ssss/index.js
+++ b/src/matrix/ssss/index.js
@@ -47,14 +47,14 @@ export async function readKey(txn) {
return new Key(new KeyDescription(keyData.id, keyAccountData), keyData.binaryKey);
}
-export async function keyFromCredential(type, credential, storage, cryptoDriver, olm) {
+export async function keyFromCredential(type, credential, storage, crypto, olm) {
const keyDescription = await readDefaultKeyDescription(storage);
if (!keyDescription) {
throw new Error("Could not find a default secret storage key in account data");
}
let key;
if (type === "phrase") {
- key = await keyFromPassphrase(keyDescription, credential, cryptoDriver);
+ key = await keyFromPassphrase(keyDescription, credential, crypto);
} else if (type === "key") {
key = keyFromRecoveryKey(olm, keyDescription, credential);
} else {
diff --git a/src/matrix/ssss/passphrase.js b/src/matrix/ssss/passphrase.js
index 1e3935a4..14f8dc0e 100644
--- a/src/matrix/ssss/passphrase.js
+++ b/src/matrix/ssss/passphrase.js
@@ -22,10 +22,10 @@ const DEFAULT_BITSIZE = 256;
/**
* @param {KeyDescription} keyDescription
* @param {string} passphrase
- * @param {CryptoDriver} cryptoDriver
+ * @param {Crypto} crypto
* @return {Key}
*/
-export async function keyFromPassphrase(keyDescription, passphrase, cryptoDriver) {
+export async function keyFromPassphrase(keyDescription, passphrase, crypto) {
const {passphraseParams} = keyDescription;
if (!passphraseParams) {
throw new Error("not a passphrase key");
@@ -35,7 +35,7 @@ export async function keyFromPassphrase(keyDescription, passphrase, cryptoDriver
}
// TODO: we should we move this to platform specific code
const textEncoder = new TextEncoder();
- const keyBits = await cryptoDriver.derive.pbkdf2(
+ const keyBits = await crypto.derive.pbkdf2(
textEncoder.encode(passphrase),
passphraseParams.iterations || DEFAULT_ITERATIONS,
// salt is just a random string, not encoded in any way
diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js
index d96dc359..3e9aca4f 100644
--- a/src/matrix/storage/common.js
+++ b/src/matrix/storage/common.js
@@ -50,3 +50,20 @@ export class StorageError extends Error {
return "StorageError";
}
}
+
+export const KeyLimits = {
+ get minStorageKey() {
+ // for indexeddb, we use unsigned 32 bit integers as keys
+ return 0;
+ },
+
+ get middleStorageKey() {
+ // for indexeddb, we use unsigned 32 bit integers as keys
+ return 0x7FFFFFFF;
+ },
+
+ get maxStorageKey() {
+ // for indexeddb, we use unsigned 32 bit integers as keys
+ return 0xFFFFFFFF;
+ }
+}
diff --git a/src/matrix/storage/idb/stores/PendingEventStore.js b/src/matrix/storage/idb/stores/PendingEventStore.js
index 59b79a05..3659299f 100644
--- a/src/matrix/storage/idb/stores/PendingEventStore.js
+++ b/src/matrix/storage/idb/stores/PendingEventStore.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import { encodeUint32, decodeUint32 } from "../utils.js";
-import {Platform} from "../../../../Platform.js";
+import {KeyLimits} from "../../common.js";
function encodeKey(roomId, queueIndex) {
return `${roomId}|${encodeUint32(queueIndex)}`;
@@ -34,8 +34,8 @@ export class PendingEventStore {
async getMaxQueueIndex(roomId) {
const range = IDBKeyRange.bound(
- encodeKey(roomId, Platform.minStorageKey),
- encodeKey(roomId, Platform.maxStorageKey),
+ encodeKey(roomId, KeyLimits.minStorageKey),
+ encodeKey(roomId, KeyLimits.maxStorageKey),
false,
false,
);
diff --git a/src/matrix/storage/idb/stores/TimelineEventStore.js b/src/matrix/storage/idb/stores/TimelineEventStore.js
index b7712337..32468b21 100644
--- a/src/matrix/storage/idb/stores/TimelineEventStore.js
+++ b/src/matrix/storage/idb/stores/TimelineEventStore.js
@@ -17,7 +17,7 @@ limitations under the License.
import {EventKey} from "../../../room/timeline/EventKey.js";
import { StorageError } from "../../common.js";
import { encodeUint32 } from "../utils.js";
-import {Platform} from "../../../../Platform.js";
+import {KeyLimits} from "../../common.js";
function encodeKey(roomId, fragmentId, eventIndex) {
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
@@ -52,7 +52,7 @@ class Range {
if (this._lower && !this._upper) {
return IDBKeyRange.bound(
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
- encodeKey(roomId, this._lower.fragmentId, Platform.maxStorageKey),
+ encodeKey(roomId, this._lower.fragmentId, KeyLimits.maxStorageKey),
this._lowerOpen,
false
);
@@ -61,7 +61,7 @@ class Range {
// also bound as we don't want to move into another roomId
if (!this._lower && this._upper) {
return IDBKeyRange.bound(
- encodeKey(roomId, this._upper.fragmentId, Platform.minStorageKey),
+ encodeKey(roomId, this._upper.fragmentId, KeyLimits.minStorageKey),
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
false,
this._upperOpen
diff --git a/src/matrix/storage/idb/stores/TimelineFragmentStore.js b/src/matrix/storage/idb/stores/TimelineFragmentStore.js
index 849e5204..8f11cb3e 100644
--- a/src/matrix/storage/idb/stores/TimelineFragmentStore.js
+++ b/src/matrix/storage/idb/stores/TimelineFragmentStore.js
@@ -15,7 +15,7 @@ limitations under the License.
*/
import { StorageError } from "../../common.js";
-import {Platform} from "../../../../Platform.js";
+import {KeyLimits} from "../../common.js";
import { encodeUint32 } from "../utils.js";
function encodeKey(roomId, fragmentId) {
@@ -30,8 +30,8 @@ export class TimelineFragmentStore {
_allRange(roomId) {
try {
return IDBKeyRange.bound(
- encodeKey(roomId, Platform.minStorageKey),
- encodeKey(roomId, Platform.maxStorageKey)
+ encodeKey(roomId, KeyLimits.minStorageKey),
+ encodeKey(roomId, KeyLimits.maxStorageKey)
);
} catch (err) {
throw new StorageError(`error from IDBKeyRange with roomId ${roomId}`, err);
diff --git a/src/matrix/storage/idb/utils.js b/src/matrix/storage/idb/utils.js
index ac9f3911..9d3a42d9 100644
--- a/src/matrix/storage/idb/utils.js
+++ b/src/matrix/storage/idb/utils.js
@@ -48,7 +48,7 @@ export async function checkNeedsSyncPromise() {
return needsSyncPromise;
}
-// storage keys are defined to be unsigned 32bit numbers in WebPlatform.js, which is assumed by idb
+// storage keys are defined to be unsigned 32bit numbers in KeyLimits, which is assumed by idb
export function encodeUint32(n) {
const hex = n.toString(16);
return "0".repeat(8 - hex.length) + hex;
diff --git a/src/platform/web/LegacyPlatform.js b/src/platform/web/LegacyPlatform.js
new file mode 100644
index 00000000..4ecea1d3
--- /dev/null
+++ b/src/platform/web/LegacyPlatform.js
@@ -0,0 +1,7 @@
+import aesjs from "../../../lib/aes-js/index.js";
+import {hkdf} from "../../utils/crypto/hkdf.js";
+import {Platform as ModernPlatform} from "./Platform.js";
+
+export function Platform(container, paths) {
+ return new ModernPlatform(container, paths, {aesjs, hkdf});
+}
diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js
new file mode 100644
index 00000000..1ecae5be
--- /dev/null
+++ b/src/platform/web/Platform.js
@@ -0,0 +1,130 @@
+/*
+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 {createFetchRequest} from "./dom/request/fetch.js";
+import {xhrRequest} from "./dom/request/xhr.js";
+import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js";
+import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js";
+import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js";
+import {RootView} from "./ui/RootView.js";
+import {Clock} from "./dom/Clock.js";
+import {ServiceWorkerHandler} from "./dom/ServiceWorkerHandler.js";
+import {History} from "./dom/History.js";
+import {OnlineStatus} from "./dom/OnlineStatus.js";
+import {Crypto} from "./dom/Crypto.js";
+import {estimateStorageUsage} from "./dom/StorageEstimate.js";
+import {WorkerPool} from "./dom/WorkerPool.js";
+
+function addScript(src) {
+ return new Promise(function (resolve, reject) {
+ var s = document.createElement("script");
+ s.setAttribute("src", src );
+ s.onload=resolve;
+ s.onerror=reject;
+ document.body.appendChild(s);
+ });
+}
+
+async function loadOlm(olmPaths) {
+ // make crypto.getRandomValues available without
+ // a prefix on IE11, needed by olm to work
+ if (window.msCrypto && !window.crypto) {
+ window.crypto = window.msCrypto;
+ }
+ if (olmPaths) {
+ if (window.WebAssembly) {
+ await addScript(olmPaths.wasmBundle);
+ await window.Olm.init({locateFile: () => olmPaths.wasm});
+ } else {
+ await addScript(olmPaths.legacyBundle);
+ await window.Olm.init();
+ }
+ return window.Olm;
+ }
+ return null;
+}
+
+// make path relative to basePath,
+// assuming it and basePath are relative to document
+function relPath(path, basePath) {
+ const idx = basePath.lastIndexOf("/");
+ const dir = idx === -1 ? "" : basePath.slice(0, idx);
+ const dirCount = dir.length ? dir.split("/").length : 0;
+ return "../".repeat(dirCount) + path;
+}
+
+async function loadOlmWorker(paths) {
+ const workerPool = new WorkerPool(paths.worker, 4);
+ await workerPool.init();
+ const path = relPath(paths.olm.legacyBundle, paths.worker);
+ await workerPool.sendAll({type: "load_olm", path});
+ const olmWorker = new OlmWorker(workerPool);
+ return olmWorker;
+}
+
+
+export class Platform {
+ constructor(container, paths, cryptoExtras = null) {
+ this._paths = paths;
+ this._container = container;
+ this.clock = new Clock();
+ this.history = new History();
+ this.onlineStatus = new OnlineStatus();
+ this._serviceWorkerHandler = null;
+ if (paths.serviceWorker && "serviceWorker" in navigator) {
+ this._serviceWorkerHandler = new ServiceWorkerHandler();
+ this._serviceWorkerHandler.registerAndStart(paths.serviceWorker);
+ }
+ this.crypto = new Crypto(cryptoExtras);
+ this.storageFactory = new StorageFactory(this._serviceWorkerHandler);
+ this.sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1");
+ this.estimateStorageUsage = estimateStorageUsage;
+ this.random = Math.random;
+ if (typeof fetch === "function") {
+ this.request = createFetchRequest(this.clock.createTimeout);
+ } else {
+ this.request = xhrRequest;
+ }
+ }
+
+ get updateService() {
+ return this._serviceWorkerHandler;
+ }
+
+ loadOlm() {
+ return loadOlm(this._paths.olm);
+ }
+
+ async loadOlmWorker() {
+ if (!window.WebAssembly) {
+ return await loadOlmWorker(this._paths);
+ }
+ }
+
+ createAndMountRootView(vm) {
+ const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
+ if (isIE11) {
+ this._container.className += " legacy";
+ }
+ window.__hydrogenViewModel = vm;
+ const view = new RootView(vm);
+ this._container.appendChild(view.mount());
+ }
+
+ setNavigation(navigation) {
+ this._serviceWorkerHandler?.setNavigation(navigation);
+ }
+}
diff --git a/src/ui/web/dom/Clock.js b/src/platform/web/dom/Clock.js
similarity index 100%
rename from src/ui/web/dom/Clock.js
rename to src/platform/web/dom/Clock.js
diff --git a/src/ui/web/dom/CryptoDriver.js b/src/platform/web/dom/Crypto.js
similarity index 92%
rename from src/ui/web/dom/CryptoDriver.js
rename to src/platform/web/dom/Crypto.js
index 5d392c03..bc02dbdc 100644
--- a/src/ui/web/dom/CryptoDriver.js
+++ b/src/platform/web/dom/Crypto.js
@@ -26,7 +26,7 @@ function subtleCryptoResult(promiseOrOp, method) {
}
}
-class CryptoHMACDriver {
+class HMACCrypto {
constructor(subtleCrypto) {
this._subtleCrypto = subtleCrypto;
}
@@ -80,10 +80,10 @@ class CryptoHMACDriver {
}
}
-class CryptoDeriveDriver {
- constructor(subtleCrypto, cryptoDriver, cryptoExtras) {
+class DeriveCrypto {
+ constructor(subtleCrypto, crypto, cryptoExtras) {
this._subtleCrypto = subtleCrypto;
- this._cryptoDriver = cryptoDriver;
+ this._crypto = crypto;
this._cryptoExtras = cryptoExtras;
}
/**
@@ -130,7 +130,7 @@ class CryptoDeriveDriver {
*/
async hkdf(key, salt, info, hash, length) {
if (!this._subtleCrypto.deriveBits) {
- return this._cryptoExtras.hkdf(this._cryptoDriver, key, salt, info, hash, length);
+ return this._cryptoExtras.hkdf(this._crypto, key, salt, info, hash, length);
}
const hkdfkey = await subtleCryptoResult(this._subtleCrypto.importKey(
'raw',
@@ -152,7 +152,7 @@ class CryptoDeriveDriver {
}
}
-class CryptoAESDriver {
+class AESCrypto {
constructor(subtleCrypto) {
this._subtleCrypto = subtleCrypto;
}
@@ -200,7 +200,7 @@ class CryptoAESDriver {
}
-class CryptoLegacyAESDriver {
+class AESLegacyCrypto {
constructor(aesjs) {
this._aesjs = aesjs;
}
@@ -237,20 +237,20 @@ function hashName(name) {
return name;
}
-export class CryptoDriver {
+export class Crypto {
constructor(cryptoExtras) {
const crypto = window.crypto || window.msCrypto;
const subtleCrypto = crypto.subtle || crypto.webkitSubtle;
this._subtleCrypto = subtleCrypto;
// not exactly guaranteeing AES-CTR support
// but in practice IE11 doesn't have this
- if (!subtleCrypto.deriveBits && cryptoExtras.aesjs) {
- this.aes = new CryptoLegacyAESDriver(cryptoExtras.aesjs);
+ if (!subtleCrypto.deriveBits && cryptoExtras?.aesjs) {
+ this.aes = new AESLegacyCrypto(cryptoExtras.aesjs);
} else {
- this.aes = new CryptoAESDriver(subtleCrypto);
+ this.aes = new AESCrypto(subtleCrypto);
}
- this.hmac = new CryptoHMACDriver(subtleCrypto);
- this.derive = new CryptoDeriveDriver(subtleCrypto, this, cryptoExtras);
+ this.hmac = new HMACCrypto(subtleCrypto);
+ this.derive = new DeriveCrypto(subtleCrypto, this, cryptoExtras);
}
/**
diff --git a/src/ui/web/dom/History.js b/src/platform/web/dom/History.js
similarity index 100%
rename from src/ui/web/dom/History.js
rename to src/platform/web/dom/History.js
diff --git a/src/ui/web/dom/OnlineStatus.js b/src/platform/web/dom/OnlineStatus.js
similarity index 100%
rename from src/ui/web/dom/OnlineStatus.js
rename to src/platform/web/dom/OnlineStatus.js
diff --git a/src/ui/web/dom/ServiceWorkerHandler.js b/src/platform/web/dom/ServiceWorkerHandler.js
similarity index 97%
rename from src/ui/web/dom/ServiceWorkerHandler.js
rename to src/platform/web/dom/ServiceWorkerHandler.js
index c3c6d4b0..b05505ea 100644
--- a/src/ui/web/dom/ServiceWorkerHandler.js
+++ b/src/platform/web/dom/ServiceWorkerHandler.js
@@ -19,15 +19,19 @@ limitations under the License.
// - UpdateService (see checkForUpdate method, and should also emit events rather than showing confirm dialog here)
// - ConcurrentAccessBlocker (see preventConcurrentSessionAccess method)
export class ServiceWorkerHandler {
- constructor({navigation}) {
+ constructor() {
this._waitingForReply = new Map();
this._messageIdCounter = 0;
+ this._navigation = null;
this._registration = null;
- this._navigation = navigation;
this._registrationPromise = null;
this._currentController = null;
}
+ setNavigation(navigation) {
+ this._navigation = navigation;
+ }
+
registerAndStart(path) {
this._registrationPromise = (async () => {
navigator.serviceWorker.addEventListener("message", this);
@@ -61,7 +65,7 @@ export class ServiceWorkerHandler {
}
_closeSessionIfNeeded(sessionId) {
- const currentSession = this._navigation.path.get("session");
+ const currentSession = this._navigation?.path.get("session");
if (sessionId && currentSession?.value === sessionId) {
return new Promise(resolve => {
const unsubscribe = this._navigation.pathObservable.subscribe(path => {
diff --git a/src/ui/web/dom/StorageEstimate.js b/src/platform/web/dom/StorageEstimate.js
similarity index 100%
rename from src/ui/web/dom/StorageEstimate.js
rename to src/platform/web/dom/StorageEstimate.js
diff --git a/src/utils/WorkerPool.js b/src/platform/web/dom/WorkerPool.js
similarity index 99%
rename from src/utils/WorkerPool.js
rename to src/platform/web/dom/WorkerPool.js
index 2f0e89b8..aeb6ca89 100644
--- a/src/utils/WorkerPool.js
+++ b/src/platform/web/dom/WorkerPool.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {AbortError} from "./error.js";
+import {AbortError} from "../../../utils/error.js";
class WorkerState {
constructor(worker) {
diff --git a/src/platform/web/dom/request/common.js b/src/platform/web/dom/request/common.js
new file mode 100644
index 00000000..d97456aa
--- /dev/null
+++ b/src/platform/web/dom/request/common.js
@@ -0,0 +1,38 @@
+/*
+Copyright 2020 Bruno Windels
+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.
+*/
+
+export 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 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");
+ }
+ }
+}
diff --git a/src/matrix/net/request/fetch.js b/src/platform/web/dom/request/fetch.js
similarity index 97%
rename from src/matrix/net/request/fetch.js
rename to src/platform/web/dom/request/fetch.js
index 0f8f9b52..13e2b32f 100644
--- a/src/matrix/net/request/fetch.js
+++ b/src/platform/web/dom/request/fetch.js
@@ -18,9 +18,9 @@ limitations under the License.
import {
AbortError,
ConnectionError
-} from "../../error.js";
-import {abortOnTimeout} from "../timeout.js";
-import {addCacheBuster} from "../common.js";
+} from "../../../../matrix/error.js";
+import {abortOnTimeout} from "./timeout.js";
+import {addCacheBuster} from "./common.js";
class RequestResult {
constructor(promise, controller) {
diff --git a/src/matrix/net/timeout.js b/src/platform/web/dom/request/timeout.js
similarity index 97%
rename from src/matrix/net/timeout.js
rename to src/platform/web/dom/request/timeout.js
index abb7fd9e..73227cfb 100644
--- a/src/matrix/net/timeout.js
+++ b/src/platform/web/dom/request/timeout.js
@@ -18,7 +18,7 @@ limitations under the License.
import {
AbortError,
ConnectionError
-} from "../error.js";
+} from "../../../../matrix/error.js";
export function abortOnTimeout(createTimeout, timeoutAmount, requestResult, responsePromise) {
diff --git a/src/matrix/net/request/xhr.js b/src/platform/web/dom/request/xhr.js
similarity index 96%
rename from src/matrix/net/request/xhr.js
rename to src/platform/web/dom/request/xhr.js
index ad4a33fc..38a189fd 100644
--- a/src/matrix/net/request/xhr.js
+++ b/src/platform/web/dom/request/xhr.js
@@ -17,8 +17,8 @@ limitations under the License.
import {
AbortError,
ConnectionError
-} from "../../error.js";
-import {addCacheBuster} from "../common.js";
+} from "../../../../matrix/error.js";
+import {addCacheBuster} from "./common.js";
class RequestResult {
constructor(promise, xhr) {
diff --git a/src/legacy-polyfill.js b/src/platform/web/legacy-polyfill.js
similarity index 91%
rename from src/legacy-polyfill.js
rename to src/platform/web/legacy-polyfill.js
index c3c8263b..ea2461f7 100644
--- a/src/legacy-polyfill.js
+++ b/src/platform/web/legacy-polyfill.js
@@ -15,8 +15,8 @@ limitations under the License.
*/
// polyfills needed for IE11
-import Promise from "../lib/es6-promise/index.js";
-import {checkNeedsSyncPromise} from "./matrix/storage/idb/utils.js";
+import Promise from "../../../lib/es6-promise/index.js";
+import {checkNeedsSyncPromise} from "../../matrix/storage/idb/utils.js";
if (typeof window.Promise === "undefined") {
window.Promise = Promise;
diff --git a/src/service-worker.template.js b/src/platform/web/service-worker.template.js
similarity index 100%
rename from src/service-worker.template.js
rename to src/platform/web/service-worker.template.js
diff --git a/src/ui/web/RootView.js b/src/platform/web/ui/RootView.js
similarity index 100%
rename from src/ui/web/RootView.js
rename to src/platform/web/ui/RootView.js
diff --git a/src/ui/web/common.js b/src/platform/web/ui/common.js
similarity index 94%
rename from src/ui/web/common.js
rename to src/platform/web/ui/common.js
index d6c4dddc..f5b71198 100644
--- a/src/ui/web/common.js
+++ b/src/platform/web/ui/common.js
@@ -14,8 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+const container = document.querySelector(".hydrogen");
+
export function spinner(t, extraClasses = undefined) {
- if (document.body.classList.contains("ie11")) {
+ if (container.classList.contains("legacy")) {
return t.div({className: "spinner"}, [
t.div(),
t.div(),
diff --git a/src/ui/web/css/avatar.css b/src/platform/web/ui/css/avatar.css
similarity index 100%
rename from src/ui/web/css/avatar.css
rename to src/platform/web/ui/css/avatar.css
diff --git a/src/ui/web/css/font.css b/src/platform/web/ui/css/font.css
similarity index 100%
rename from src/ui/web/css/font.css
rename to src/platform/web/ui/css/font.css
diff --git a/src/ui/web/css/form.css b/src/platform/web/ui/css/form.css
similarity index 100%
rename from src/ui/web/css/form.css
rename to src/platform/web/ui/css/form.css
diff --git a/src/ui/web/css/layout.css b/src/platform/web/ui/css/layout.css
similarity index 100%
rename from src/ui/web/css/layout.css
rename to src/platform/web/ui/css/layout.css
diff --git a/src/ui/web/css/left-panel.css b/src/platform/web/ui/css/left-panel.css
similarity index 100%
rename from src/ui/web/css/left-panel.css
rename to src/platform/web/ui/css/left-panel.css
diff --git a/src/ui/web/css/login.css b/src/platform/web/ui/css/login.css
similarity index 100%
rename from src/ui/web/css/login.css
rename to src/platform/web/ui/css/login.css
diff --git a/src/ui/web/css/main.css b/src/platform/web/ui/css/main.css
similarity index 100%
rename from src/ui/web/css/main.css
rename to src/platform/web/ui/css/main.css
diff --git a/src/ui/web/css/room.css b/src/platform/web/ui/css/room.css
similarity index 100%
rename from src/ui/web/css/room.css
rename to src/platform/web/ui/css/room.css
diff --git a/src/ui/web/css/spinner.css b/src/platform/web/ui/css/spinner.css
similarity index 84%
rename from src/ui/web/css/spinner.css
rename to src/platform/web/ui/css/spinner.css
index 1548b9b6..b649cf0f 100644
--- a/src/ui/web/css/spinner.css
+++ b/src/platform/web/ui/css/spinner.css
@@ -32,7 +32,7 @@ limitations under the License.
}
}
-.not-ie11 .spinner circle {
+.hydrogen:not(.legacy) .spinner circle {
transform-origin: 50% 50%;
animation-name: spinner;
animation-duration: 2s;
@@ -45,35 +45,35 @@ limitations under the License.
stroke-linecap: butt;
}
-.ie11 .spinner {
+.hydrogen.legacy .spinner {
display: inline-block;
position: relative;
}
-.ie11 .spinner div {
+.hydrogen.legacy .spinner div {
box-sizing: border-box;
display: block;
position: absolute;
padding: 2px;
border: 2px solid currentcolor;
border-radius: 50%;
- animation: ie-spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ animation: legacy-spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: currentcolor transparent transparent transparent;
width: var(--size);
height: var(--size);
}
-.ie11 .spinner div:nth-child(1) {
+.hydrogen.legacy .spinner div:nth-child(1) {
animation-delay: -0.45s;
}
-.ie11 .spinner div:nth-child(2) {
+.hydrogen.legacy .spinner div:nth-child(2) {
animation-delay: -0.3s;
}
-.ie11 .spinner div:nth-child(3) {
+.hydrogen.legacy .spinner div:nth-child(3) {
animation-delay: -0.15s;
}
-@keyframes ie-spinner {
+@keyframes legacy-spinner {
0% {
transform: rotate(0deg);
}
diff --git a/src/ui/web/css/status.css b/src/platform/web/ui/css/status.css
similarity index 100%
rename from src/ui/web/css/status.css
rename to src/platform/web/ui/css/status.css
diff --git a/src/ui/web/css/themes/README.md b/src/platform/web/ui/css/themes/README.md
similarity index 100%
rename from src/ui/web/css/themes/README.md
rename to src/platform/web/ui/css/themes/README.md
diff --git a/src/ui/web/css/themes/bubbles/theme.css b/src/platform/web/ui/css/themes/bubbles/theme.css
similarity index 100%
rename from src/ui/web/css/themes/bubbles/theme.css
rename to src/platform/web/ui/css/themes/bubbles/theme.css
diff --git a/src/ui/web/css/themes/element/element-logo.svg b/src/platform/web/ui/css/themes/element/element-logo.svg
similarity index 100%
rename from src/ui/web/css/themes/element/element-logo.svg
rename to src/platform/web/ui/css/themes/element/element-logo.svg
diff --git a/src/ui/web/css/themes/element/icons/chevron-left.svg b/src/platform/web/ui/css/themes/element/icons/chevron-left.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/chevron-left.svg
rename to src/platform/web/ui/css/themes/element/icons/chevron-left.svg
diff --git a/src/ui/web/css/themes/element/icons/chevron-right.svg b/src/platform/web/ui/css/themes/element/icons/chevron-right.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/chevron-right.svg
rename to src/platform/web/ui/css/themes/element/icons/chevron-right.svg
diff --git a/src/ui/web/css/themes/element/icons/clear.svg b/src/platform/web/ui/css/themes/element/icons/clear.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/clear.svg
rename to src/platform/web/ui/css/themes/element/icons/clear.svg
diff --git a/src/ui/web/css/themes/element/icons/disable-grid.svg b/src/platform/web/ui/css/themes/element/icons/disable-grid.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/disable-grid.svg
rename to src/platform/web/ui/css/themes/element/icons/disable-grid.svg
diff --git a/src/ui/web/css/themes/element/icons/dismiss.svg b/src/platform/web/ui/css/themes/element/icons/dismiss.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/dismiss.svg
rename to src/platform/web/ui/css/themes/element/icons/dismiss.svg
diff --git a/src/ui/web/css/themes/element/icons/enable-grid.svg b/src/platform/web/ui/css/themes/element/icons/enable-grid.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/enable-grid.svg
rename to src/platform/web/ui/css/themes/element/icons/enable-grid.svg
diff --git a/src/ui/web/css/themes/element/icons/search.svg b/src/platform/web/ui/css/themes/element/icons/search.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/search.svg
rename to src/platform/web/ui/css/themes/element/icons/search.svg
diff --git a/src/ui/web/css/themes/element/icons/send.svg b/src/platform/web/ui/css/themes/element/icons/send.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/send.svg
rename to src/platform/web/ui/css/themes/element/icons/send.svg
diff --git a/src/ui/web/css/themes/element/icons/settings.svg b/src/platform/web/ui/css/themes/element/icons/settings.svg
similarity index 100%
rename from src/ui/web/css/themes/element/icons/settings.svg
rename to src/platform/web/ui/css/themes/element/icons/settings.svg
diff --git a/src/ui/web/css/themes/element/inter.css b/src/platform/web/ui/css/themes/element/inter.css
similarity index 100%
rename from src/ui/web/css/themes/element/inter.css
rename to src/platform/web/ui/css/themes/element/inter.css
diff --git a/src/ui/web/css/themes/element/inter/Inter-Black.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Black.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Black.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Black.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Black.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Black.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Black.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Black.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-BlackItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-BlackItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-BlackItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-BlackItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Bold.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Bold.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Bold.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Bold.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-BoldItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-BoldItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-BoldItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-BoldItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraBold.woff b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraBold.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraBold.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraBold.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraBoldItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraBoldItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraBoldItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraBoldItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraLight.woff b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraLight.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraLight.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraLight.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraLightItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraLightItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-ExtraLightItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ExtraLightItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Italic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Italic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Italic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Italic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Light.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Light.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Light.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Light.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Light.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Light.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Light.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Light.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-LightItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-LightItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-LightItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-LightItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Medium.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Medium.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Medium.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Medium.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-MediumItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-MediumItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-MediumItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-MediumItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Regular.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Regular.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Regular.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Regular.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-SemiBold.woff b/src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-SemiBold.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-SemiBold.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-SemiBold.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-SemiBoldItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-SemiBoldItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-SemiBoldItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-SemiBoldItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-Thin.woff b/src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Thin.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-Thin.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-Thin.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff2
diff --git a/src/ui/web/css/themes/element/inter/Inter-ThinItalic.woff b/src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ThinItalic.woff
rename to src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff
diff --git a/src/ui/web/css/themes/element/inter/Inter-ThinItalic.woff2 b/src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff2
similarity index 100%
rename from src/ui/web/css/themes/element/inter/Inter-ThinItalic.woff2
rename to src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff2
diff --git a/src/ui/web/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css
similarity index 100%
rename from src/ui/web/css/themes/element/theme.css
rename to src/platform/web/ui/css/themes/element/theme.css
diff --git a/src/ui/web/css/timeline.css b/src/platform/web/ui/css/timeline.css
similarity index 100%
rename from src/ui/web/css/timeline.css
rename to src/platform/web/ui/css/timeline.css
diff --git a/src/ui/web/general/ListView.js b/src/platform/web/ui/general/ListView.js
similarity index 100%
rename from src/ui/web/general/ListView.js
rename to src/platform/web/ui/general/ListView.js
diff --git a/src/ui/web/general/StaticView.js b/src/platform/web/ui/general/StaticView.js
similarity index 100%
rename from src/ui/web/general/StaticView.js
rename to src/platform/web/ui/general/StaticView.js
diff --git a/src/ui/web/general/SwitchView.js b/src/platform/web/ui/general/SwitchView.js
similarity index 100%
rename from src/ui/web/general/SwitchView.js
rename to src/platform/web/ui/general/SwitchView.js
diff --git a/src/ui/web/general/TemplateView.js b/src/platform/web/ui/general/TemplateView.js
similarity index 100%
rename from src/ui/web/general/TemplateView.js
rename to src/platform/web/ui/general/TemplateView.js
diff --git a/src/ui/web/general/error.js b/src/platform/web/ui/general/error.js
similarity index 100%
rename from src/ui/web/general/error.js
rename to src/platform/web/ui/general/error.js
diff --git a/src/ui/web/general/html.js b/src/platform/web/ui/general/html.js
similarity index 100%
rename from src/ui/web/general/html.js
rename to src/platform/web/ui/general/html.js
diff --git a/src/ui/web/login/LoginView.js b/src/platform/web/ui/login/LoginView.js
similarity index 100%
rename from src/ui/web/login/LoginView.js
rename to src/platform/web/ui/login/LoginView.js
diff --git a/src/ui/web/login/SessionLoadStatusView.js b/src/platform/web/ui/login/SessionLoadStatusView.js
similarity index 100%
rename from src/ui/web/login/SessionLoadStatusView.js
rename to src/platform/web/ui/login/SessionLoadStatusView.js
diff --git a/src/ui/web/login/SessionLoadView.js b/src/platform/web/ui/login/SessionLoadView.js
similarity index 100%
rename from src/ui/web/login/SessionLoadView.js
rename to src/platform/web/ui/login/SessionLoadView.js
diff --git a/src/ui/web/login/SessionPickerView.js b/src/platform/web/ui/login/SessionPickerView.js
similarity index 100%
rename from src/ui/web/login/SessionPickerView.js
rename to src/platform/web/ui/login/SessionPickerView.js
diff --git a/src/ui/web/login/common.js b/src/platform/web/ui/login/common.js
similarity index 100%
rename from src/ui/web/login/common.js
rename to src/platform/web/ui/login/common.js
diff --git a/src/ui/web/session/RoomGridView.js b/src/platform/web/ui/session/RoomGridView.js
similarity index 100%
rename from src/ui/web/session/RoomGridView.js
rename to src/platform/web/ui/session/RoomGridView.js
diff --git a/src/ui/web/session/SessionStatusView.js b/src/platform/web/ui/session/SessionStatusView.js
similarity index 100%
rename from src/ui/web/session/SessionStatusView.js
rename to src/platform/web/ui/session/SessionStatusView.js
diff --git a/src/ui/web/session/SessionView.js b/src/platform/web/ui/session/SessionView.js
similarity index 100%
rename from src/ui/web/session/SessionView.js
rename to src/platform/web/ui/session/SessionView.js
diff --git a/src/ui/web/session/leftpanel/LeftPanelView.js b/src/platform/web/ui/session/leftpanel/LeftPanelView.js
similarity index 100%
rename from src/ui/web/session/leftpanel/LeftPanelView.js
rename to src/platform/web/ui/session/leftpanel/LeftPanelView.js
diff --git a/src/ui/web/session/leftpanel/RoomTileView.js b/src/platform/web/ui/session/leftpanel/RoomTileView.js
similarity index 100%
rename from src/ui/web/session/leftpanel/RoomTileView.js
rename to src/platform/web/ui/session/leftpanel/RoomTileView.js
diff --git a/src/ui/web/session/room/MessageComposer.js b/src/platform/web/ui/session/room/MessageComposer.js
similarity index 100%
rename from src/ui/web/session/room/MessageComposer.js
rename to src/platform/web/ui/session/room/MessageComposer.js
diff --git a/src/ui/web/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js
similarity index 100%
rename from src/ui/web/session/room/RoomView.js
rename to src/platform/web/ui/session/room/RoomView.js
diff --git a/src/ui/web/session/room/TimelineList.js b/src/platform/web/ui/session/room/TimelineList.js
similarity index 100%
rename from src/ui/web/session/room/TimelineList.js
rename to src/platform/web/ui/session/room/TimelineList.js
diff --git a/src/ui/web/session/room/TimelineLoadingView.js b/src/platform/web/ui/session/room/TimelineLoadingView.js
similarity index 100%
rename from src/ui/web/session/room/TimelineLoadingView.js
rename to src/platform/web/ui/session/room/TimelineLoadingView.js
diff --git a/src/ui/web/session/room/timeline/AnnouncementView.js b/src/platform/web/ui/session/room/timeline/AnnouncementView.js
similarity index 100%
rename from src/ui/web/session/room/timeline/AnnouncementView.js
rename to src/platform/web/ui/session/room/timeline/AnnouncementView.js
diff --git a/src/ui/web/session/room/timeline/GapView.js b/src/platform/web/ui/session/room/timeline/GapView.js
similarity index 100%
rename from src/ui/web/session/room/timeline/GapView.js
rename to src/platform/web/ui/session/room/timeline/GapView.js
diff --git a/src/ui/web/session/room/timeline/ImageView.js b/src/platform/web/ui/session/room/timeline/ImageView.js
similarity index 100%
rename from src/ui/web/session/room/timeline/ImageView.js
rename to src/platform/web/ui/session/room/timeline/ImageView.js
diff --git a/src/ui/web/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js
similarity index 100%
rename from src/ui/web/session/room/timeline/TextMessageView.js
rename to src/platform/web/ui/session/room/timeline/TextMessageView.js
diff --git a/src/ui/web/session/room/timeline/TimelineTile.js b/src/platform/web/ui/session/room/timeline/TimelineTile.js
similarity index 100%
rename from src/ui/web/session/room/timeline/TimelineTile.js
rename to src/platform/web/ui/session/room/timeline/TimelineTile.js
diff --git a/src/ui/web/session/room/timeline/common.js b/src/platform/web/ui/session/room/timeline/common.js
similarity index 100%
rename from src/ui/web/session/room/timeline/common.js
rename to src/platform/web/ui/session/room/timeline/common.js
diff --git a/src/ui/web/session/settings/SessionBackupSettingsView.js b/src/platform/web/ui/session/settings/SessionBackupSettingsView.js
similarity index 100%
rename from src/ui/web/session/settings/SessionBackupSettingsView.js
rename to src/platform/web/ui/session/settings/SessionBackupSettingsView.js
diff --git a/src/ui/web/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js
similarity index 100%
rename from src/ui/web/session/settings/SettingsView.js
rename to src/platform/web/ui/session/settings/SettingsView.js
diff --git a/src/ui/web/view-gallery.html b/src/platform/web/ui/view-gallery.html
similarity index 100%
rename from src/ui/web/view-gallery.html
rename to src/platform/web/ui/view-gallery.html
diff --git a/src/worker.js b/src/platform/web/worker/main.js
similarity index 100%
rename from src/worker.js
rename to src/platform/web/worker/main.js
diff --git a/src/worker-polyfill.js b/src/platform/web/worker/polyfill.js
similarity index 97%
rename from src/worker-polyfill.js
rename to src/platform/web/worker/polyfill.js
index 28541188..d7f4b0d2 100644
--- a/src/worker-polyfill.js
+++ b/src/platform/web/worker/polyfill.js
@@ -19,7 +19,7 @@ limitations under the License.
// just enough to run olm, have promises and async/await
// load this first just in case anything else depends on it
-import Promise from "../lib/es6-promise/index.js";
+import Promise from "../../../../lib/es6-promise/index.js";
// not calling checkNeedsSyncPromise from here as we don't do any idb in the worker,
// mainly because IE doesn't handle multiple concurrent connections well
self.Promise = Promise;
diff --git a/src/ui/web/WebPlatform.js b/src/ui/web/WebPlatform.js
deleted file mode 100644
index 22a41323..00000000
--- a/src/ui/web/WebPlatform.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-Copyright 2020 Bruno Windels
-
-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 WebPlatform = {
- get minStorageKey() {
- // for indexeddb, we use unsigned 32 bit integers as keys
- return 0;
- },
-
- get middleStorageKey() {
- // for indexeddb, we use unsigned 32 bit integers as keys
- return 0x7FFFFFFF;
- },
-
- get maxStorageKey() {
- // for indexeddb, we use unsigned 32 bit integers as keys
- return 0xFFFFFFFF;
- },
-
- delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
-}