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)); - } -}