├── .dockerignore ├── prototypes ├── ie11.css ├── non-ie11.css ├── me.jpg ├── manifest.appcache ├── tools │ └── package.json ├── ie11-hashchange.html ├── base256.html ├── online.html └── ie11-textdecoder.html ├── scripts ├── sdk │ ├── test │ │ ├── .gitignore │ │ ├── deps.d.ts │ │ ├── package.json │ │ ├── index.html │ │ ├── test-sdk-in-esm-vite-build-env.js │ │ ├── test-sdk-in-commonjs-env.js │ │ └── esm-entry.ts │ ├── create-manifest.js │ ├── base-manifest.json │ ├── build.sh │ └── transform-paths.js ├── cleanup.sh ├── package-overrides │ ├── safe-buffer │ │ └── index.js │ └── buffer │ │ └── index.js ├── test-derived-theme │ └── test-theme.sh ├── package.sh ├── .eslintrc.js ├── release.sh ├── test-app.sh └── postcss │ └── tests │ └── common.js ├── doc ├── architecture │ ├── UI │ │ └── index.md │ ├── images │ │ ├── coloring-process.png │ │ ├── svg-icon-example.png │ │ └── theming-architecture.png │ ├── persisted-network-calls.md │ └── sync-updates.md ├── problem solving │ ├── domexception_mapping.md │ └── IMPORT-ISSUES.md └── implementation planning │ ├── html-messages.md │ ├── READ-RECEIPTS.md │ ├── timeline-member.md │ ├── LOGIN.md │ ├── DESIGN.md │ ├── RELEASE.md │ ├── session-container.md │ ├── background-tasks.md │ ├── CATCHUP-BACKFILL.md │ ├── LOCAL-ECHO-STATE.md │ ├── ROOM-VERSIONS.md │ ├── PUSH.md │ └── REPLIES.md ├── playwright ├── plugins │ ├── synapsedocker │ │ └── templates │ │ │ ├── consent │ │ │ ├── README.md │ │ │ └── res │ │ │ │ └── templates │ │ │ │ └── privacy │ │ │ │ └── en │ │ │ │ ├── success.html │ │ │ │ └── 1.0.html │ │ │ ├── default │ │ │ └── README.md │ │ │ └── COPYME │ │ │ └── README.md │ └── dex │ │ └── template │ │ └── dev.db ├── tests │ └── startup.spec.ts └── global-setup.ts ├── src ├── platform │ └── web │ │ ├── assets │ │ ├── icon.png │ │ ├── icon-maskable.png │ │ ├── config.json │ │ ├── manifest.json │ │ ├── icon.svg │ │ └── icon-maskable.svg │ │ ├── public │ │ └── icon.png │ │ ├── ui │ │ ├── css │ │ │ ├── themes │ │ │ │ ├── element │ │ │ │ │ ├── inter │ │ │ │ │ │ ├── Inter-Black.woff │ │ │ │ │ │ ├── Inter-Black.woff2 │ │ │ │ │ │ ├── Inter-Bold.woff │ │ │ │ │ │ ├── Inter-Bold.woff2 │ │ │ │ │ │ ├── Inter-Italic.woff │ │ │ │ │ │ ├── Inter-Light.woff │ │ │ │ │ │ ├── Inter-Light.woff2 │ │ │ │ │ │ ├── Inter-Medium.woff │ │ │ │ │ │ ├── Inter-Thin.woff │ │ │ │ │ │ ├── Inter-Thin.woff2 │ │ │ │ │ │ ├── Inter-Italic.woff2 │ │ │ │ │ │ ├── Inter-Medium.woff2 │ │ │ │ │ │ ├── Inter-Regular.woff │ │ │ │ │ │ ├── Inter-Regular.woff2 │ │ │ │ │ │ ├── Inter-SemiBold.woff │ │ │ │ │ │ ├── Inter-BlackItalic.woff │ │ │ │ │ │ ├── Inter-BoldItalic.woff │ │ │ │ │ │ ├── Inter-BoldItalic.woff2 │ │ │ │ │ │ ├── Inter-ExtraBold.woff │ │ │ │ │ │ ├── Inter-ExtraBold.woff2 │ │ │ │ │ │ ├── Inter-ExtraLight.woff │ │ │ │ │ │ ├── Inter-ExtraLight.woff2 │ │ │ │ │ │ ├── Inter-LightItalic.woff │ │ │ │ │ │ ├── Inter-SemiBold.woff2 │ │ │ │ │ │ ├── Inter-ThinItalic.woff │ │ │ │ │ │ ├── Inter-ThinItalic.woff2 │ │ │ │ │ │ ├── Inter-BlackItalic.woff2 │ │ │ │ │ │ ├── Inter-LightItalic.woff2 │ │ │ │ │ │ ├── Inter-MediumItalic.woff │ │ │ │ │ │ ├── Inter-MediumItalic.woff2 │ │ │ │ │ │ ├── Inter-ExtraBoldItalic.woff │ │ │ │ │ │ ├── Inter-ExtraBoldItalic.woff2 │ │ │ │ │ │ ├── Inter-ExtraLightItalic.woff │ │ │ │ │ │ ├── Inter-SemiBoldItalic.woff │ │ │ │ │ │ ├── Inter-SemiBoldItalic.woff2 │ │ │ │ │ │ └── Inter-ExtraLightItalic.woff2 │ │ │ │ │ ├── icons │ │ │ │ │ │ ├── e2ee-normal.svg │ │ │ │ │ │ ├── chevron-right.svg │ │ │ │ │ │ ├── chevron-thin-left.svg │ │ │ │ │ │ ├── encryption-status.svg │ │ │ │ │ │ ├── clear.svg │ │ │ │ │ │ ├── dismiss.svg │ │ │ │ │ │ ├── video-call.svg │ │ │ │ │ │ ├── chevron-small.svg │ │ │ │ │ │ ├── info.svg │ │ │ │ │ │ ├── paperclip.svg │ │ │ │ │ │ ├── cam-unmuted.svg │ │ │ │ │ │ ├── send.svg │ │ │ │ │ │ ├── disable-grid.svg │ │ │ │ │ │ ├── verified.svg │ │ │ │ │ │ ├── e2ee-disabled.svg │ │ │ │ │ │ ├── plus.svg │ │ │ │ │ │ ├── verification-error.svg │ │ │ │ │ │ ├── vertical-ellipsis.svg │ │ │ │ │ │ ├── chevron-left.svg │ │ │ │ │ │ ├── search.svg │ │ │ │ │ │ ├── cam-muted.svg │ │ │ │ │ │ ├── hangup.svg │ │ │ │ │ │ ├── voice-call.svg │ │ │ │ │ │ ├── enable-grid.svg │ │ │ │ │ │ ├── mic-unmuted.svg │ │ │ │ │ │ └── chevron-down.svg │ │ │ │ │ ├── error.css │ │ │ │ │ ├── element-logo.svg │ │ │ │ │ └── manifest.json │ │ │ │ └── README.md │ │ │ ├── popup.css │ │ │ ├── status.css │ │ │ ├── font.css │ │ │ ├── form.css │ │ │ ├── left-panel.css │ │ │ └── main.css │ │ ├── session │ │ │ ├── room │ │ │ │ ├── DisabledComposerView.js │ │ │ │ ├── timeline │ │ │ │ │ ├── MissingAttachmentView.js │ │ │ │ │ ├── LocationView.js │ │ │ │ │ ├── ImageView.js │ │ │ │ │ ├── DateHeaderView.ts │ │ │ │ │ ├── AnnouncementView.js │ │ │ │ │ ├── FileView.js │ │ │ │ │ ├── RedactedView.js │ │ │ │ │ ├── ReactionsView.js │ │ │ │ │ └── GapView.js │ │ │ │ └── TimelineLoadingView.js │ │ │ ├── rightpanel │ │ │ │ ├── MemberTileView.js │ │ │ │ └── MemberListView.js │ │ │ └── SessionStatusView.js │ │ ├── general │ │ │ ├── LoadingView.js │ │ │ ├── StaticView.js │ │ │ └── types.ts │ │ ├── login │ │ │ ├── SessionLoadView.js │ │ │ ├── CompleteSSOView.js │ │ │ └── common.js │ │ └── common.js │ │ ├── utils │ │ ├── Base58.js │ │ ├── Encoding.js │ │ └── Base64.js │ │ ├── dom │ │ ├── StorageEstimate.js │ │ ├── UTF8.js │ │ └── OnlineStatus.js │ │ ├── LegacyPlatform.js │ │ ├── sdk │ │ └── paths │ │ │ └── vite.js │ │ ├── theming │ │ ├── parsers │ │ │ └── types.ts │ │ └── shared │ │ │ ├── svg-colorizer.mjs │ │ │ └── color.mjs │ │ ├── legacy-polyfill.js │ │ └── index.html ├── domain │ ├── session │ │ ├── toast │ │ │ ├── IToastCollection.ts │ │ │ └── BaseToastNotificationViewModel.ts │ │ ├── leftpanel │ │ │ ├── common.js │ │ │ └── RoomFilter.js │ │ ├── common.js │ │ ├── room │ │ │ ├── timeline │ │ │ │ ├── tiles │ │ │ │ │ ├── RoomNameTile.js │ │ │ │ │ ├── EncryptionEnabledTile.js │ │ │ │ │ ├── MissingAttachmentTile.js │ │ │ │ │ ├── ImageTile.js │ │ │ │ │ ├── VideoTile.js │ │ │ │ │ ├── RedactedTile.js │ │ │ │ │ └── EncryptedEventTile.js │ │ │ │ ├── UpdateAction.js │ │ │ │ └── linkify │ │ │ │ │ └── regex.ts │ │ │ └── README.md │ │ └── verification │ │ │ └── stages │ │ │ ├── MissingKeysViewModel.ts │ │ │ ├── DismissibleVerificationViewModel.ts │ │ │ └── VerifyEmojisViewModel.ts │ ├── AvatarSource.ts │ ├── ErrorViewModel.ts │ └── login │ │ └── StartSSOLoginViewModel.ts ├── index.html ├── matrix │ ├── login │ │ ├── index.ts │ │ ├── LoginMethod.ts │ │ ├── SSOLoginHelper.ts │ │ ├── TokenLoginMethod.ts │ │ └── PasswordLoginMethod.ts │ ├── storage │ │ ├── idb │ │ │ ├── stores │ │ │ │ ├── common.ts │ │ │ │ ├── UserIdentityStore.ts │ │ │ │ ├── OutboundGroupSessionStore.ts │ │ │ │ ├── SharedSecretStore.ts │ │ │ │ ├── AccountDataStore.ts │ │ │ │ ├── InviteStore.ts │ │ │ │ └── RoomSummaryStore.ts │ │ │ ├── types.ts │ │ │ ├── quirks.ts │ │ │ └── export.ts │ │ └── types.ts │ ├── User.js │ ├── verification │ │ └── SAS │ │ │ ├── VerificationCancelledError.ts │ │ │ ├── stages │ │ │ ├── constants.ts │ │ │ ├── SendDoneStage.ts │ │ │ ├── SendReadyStage.ts │ │ │ ├── SendRequestVerificationStage.ts │ │ │ └── SendKeyStage.ts │ │ │ ├── types.ts │ │ │ ├── channel │ │ │ └── types.ts │ │ │ ├── mac.ts │ │ │ └── SASRequest.ts │ ├── ServerFeatures.js │ ├── registration │ │ ├── stages │ │ │ ├── DummyAuth.ts │ │ │ ├── TermsAuth.ts │ │ │ ├── TokenAuth.ts │ │ │ └── BaseRegistrationStage.ts │ │ └── types.ts │ ├── e2ee │ │ ├── megolm │ │ │ └── decryption │ │ │ │ ├── README.md │ │ │ │ └── ReplayDetectionEntry.ts │ │ └── olm │ │ │ └── types.ts │ ├── room │ │ ├── timeline │ │ │ ├── persistence │ │ │ │ └── common.js │ │ │ ├── Direction.ts │ │ │ └── entries │ │ │ │ └── NonPersistedEventEntry.js │ │ ├── members │ │ │ └── MemberList.js │ │ ├── state │ │ │ └── types.ts │ │ └── joinRoom.ts │ ├── net │ │ └── types │ │ │ └── response.ts │ └── error.js ├── utils │ ├── error.ts │ ├── enum.ts │ ├── RetainedValue.ts │ ├── formatSize.ts │ ├── Deferred.ts │ ├── mergeMap.ts │ ├── recursivelyAssign.ts │ ├── sortedIndex.ts │ ├── crypto │ │ └── hkdf.ts │ └── timeFormatting.ts ├── mocks │ ├── poll.js │ ├── Request.js │ ├── event.js │ └── HomeServer.js ├── observable │ ├── index.ts │ ├── value │ │ ├── RetainedObservableValue.ts │ │ ├── index.ts │ │ └── EventObservableValue.ts │ ├── map │ │ ├── index.ts │ │ └── ObservableValueMap.ts │ └── list │ │ └── common.ts └── logging │ ├── utils.ts │ └── LogFilter.ts ├── .gitignore ├── LICENSE-COMMERCIAL ├── Dockerfile-dev ├── docker └── dynamic-config.sh ├── .editorconfig ├── tsconfig-declaration.json ├── tsconfig.json ├── TODO.md ├── .github ├── CODEOWNERS └── workflows │ └── docker-publish.yml ├── codestyle.md ├── playwright.config.ts ├── .eslintrc.js ├── .ts-eslintrc.js └── Dockerfile /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | target 3 | -------------------------------------------------------------------------------- /prototypes/ie11.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /prototypes/non-ie11.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: green; 3 | } 4 | -------------------------------------------------------------------------------- /scripts/sdk/test/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /scripts/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Remove icons created in .tmp 3 | rm -rf .tmp 4 | -------------------------------------------------------------------------------- /prototypes/me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/prototypes/me.jpg -------------------------------------------------------------------------------- /prototypes/manifest.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # v1 3 | /responsive-layout-flex.html 4 | /me.jpg 5 | -------------------------------------------------------------------------------- /scripts/package-overrides/safe-buffer/index.js: -------------------------------------------------------------------------------- 1 | import Buffer from "buffer"; 2 | export {Buffer}; 3 | -------------------------------------------------------------------------------- /doc/architecture/UI/index.md: -------------------------------------------------------------------------------- 1 | # Index for UI code 2 | 3 | 1. [Rendering DOM elements](./render-dom-elements.md) 4 | -------------------------------------------------------------------------------- /playwright/plugins/synapsedocker/templates/consent/README.md: -------------------------------------------------------------------------------- 1 | A synapse configured with user privacy consent enabled 2 | -------------------------------------------------------------------------------- /playwright/plugins/synapsedocker/templates/default/README.md: -------------------------------------------------------------------------------- 1 | A synapse configured with user privacy consent disabled 2 | -------------------------------------------------------------------------------- /src/platform/web/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/assets/icon.png -------------------------------------------------------------------------------- /src/platform/web/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/public/icon.png -------------------------------------------------------------------------------- /doc/problem solving/domexception_mapping.md: -------------------------------------------------------------------------------- 1 | err.name: explanation 2 | DataError: parameters to idb request where invalid 3 | -------------------------------------------------------------------------------- /playwright/plugins/dex/template/dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/playwright/plugins/dex/template/dev.db -------------------------------------------------------------------------------- /src/platform/web/assets/icon-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/assets/icon-maskable.png -------------------------------------------------------------------------------- /doc/architecture/images/coloring-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/doc/architecture/images/coloring-process.png -------------------------------------------------------------------------------- /doc/architecture/images/svg-icon-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/doc/architecture/images/svg-icon-example.png -------------------------------------------------------------------------------- /scripts/sdk/test/deps.d.ts: -------------------------------------------------------------------------------- 1 | // Keep TypeScripts from complaining about hydrogen-view-sdk not having types yet 2 | declare module "hydrogen-view-sdk"; 3 | -------------------------------------------------------------------------------- /doc/architecture/images/theming-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/doc/architecture/images/theming-architecture.png -------------------------------------------------------------------------------- /playwright/plugins/synapsedocker/templates/COPYME/README.md: -------------------------------------------------------------------------------- 1 | # Meta-template for synapse templates 2 | 3 | To make another template, you can copy this directory 4 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Black.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Black.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Bold.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Light.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Light.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Thin.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Italic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Medium.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-Regular.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff -------------------------------------------------------------------------------- /doc/implementation planning/html-messages.md: -------------------------------------------------------------------------------- 1 | message model: 2 | - paragraphs (p, h1, code block, quote, ...) 3 | - lines 4 | - parts (inline markup), which can be recursive 5 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-BoldItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBold.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLight.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-SemiBold.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ThinItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-BlackItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-LightItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-MediumItalic.woff2 -------------------------------------------------------------------------------- /scripts/sdk/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-sdk", 3 | "version": "0.0.0", 4 | "description": "", 5 | "dependencies": { 6 | "hydrogen-view-sdk": "link:../../../target" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/hydrogen-web/HEAD/src/platform/web/ui/css/themes/element/inter/Inter-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | .DS_Store 4 | node_modules 5 | fetchlogs 6 | sessionexports 7 | bundle.js 8 | target 9 | lib 10 | *.tar.gz 11 | .eslintcache 12 | .tmp 13 | playwright/synapselogs 14 | -------------------------------------------------------------------------------- /doc/implementation planning/READ-RECEIPTS.md: -------------------------------------------------------------------------------- 1 | # Read receipts 2 | 3 | ## UI 4 | 5 | For the expanding avatars, trimmed at 5 or so, we could use css grid and switch from the right most cell to a cell that covers the whole width when clicking. -------------------------------------------------------------------------------- /doc/implementation planning/timeline-member.md: -------------------------------------------------------------------------------- 1 | ## Get member for timeline event 2 | 3 | so when writing sync, we persist the display name and avatar 4 | 5 | the server might or might not support lazy loading 6 | 7 | if it is a room we just joined -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/README.md: -------------------------------------------------------------------------------- 1 | things that go in the theme: 2 | - margin specialization 3 | - padding 4 | - colors (foreground, background, border, ...) 5 | - border-radius 6 | - font faces, weights and sizes 7 | - alignment 8 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/e2ee-normal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playwright/plugins/synapsedocker/templates/consent/res/templates/privacy/en/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Privacy policy 5 | 6 | 7 |

Danke schon

8 | 9 | -------------------------------------------------------------------------------- /doc/implementation planning/LOGIN.md: -------------------------------------------------------------------------------- 1 | LoginView 2 | LoginViewModel 3 | SessionPickerView 4 | SessionPickerViewModel 5 | 6 | matrix: 7 | SessionStorage (could be in keychain, ... for now we go with localstorage) 8 | getAll() 9 | 10 | Login 11 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE-COMMERCIAL: -------------------------------------------------------------------------------- 1 | Licensees holding a valid commercial license with Element may use this 2 | software in accordance with the terms contained in a written agreement 3 | between you and Element. 4 | 5 | To purchase a commercial license please contact our sales team at 6 | licensing@element.io -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/chevron-thin-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/domain/session/toast/IToastCollection.ts: -------------------------------------------------------------------------------- 1 | import {ObservableArray} from "../../../observable"; 2 | import type {BaseToastNotificationViewModel} from "./BaseToastNotificationViewModel"; 3 | 4 | export interface IToastCollection { 5 | toastViewModels: ObservableArray; 6 | } 7 | -------------------------------------------------------------------------------- /scripts/test-derived-theme/test-theme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cp scripts/test-derived-theme/theme.json target/assets/theme-customer.json 3 | cat target/config.json | jq '.themeManifests += ["assets/theme-customer.json"]' | cat > target/config.temp.json 4 | rm target/config.json 5 | mv target/config.temp.json target/config.json 6 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/matrix/login/index.ts: -------------------------------------------------------------------------------- 1 | import {ILoginMethod} from "./LoginMethod"; 2 | import {PasswordLoginMethod} from "./PasswordLoginMethod"; 3 | import {SSOLoginHelper} from "./SSOLoginHelper"; 4 | import {TokenLoginMethod} from "./TokenLoginMethod"; 5 | 6 | 7 | export {PasswordLoginMethod, SSOLoginHelper, TokenLoginMethod, ILoginMethod}; -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/encryption-status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/implementation planning/DESIGN.md: -------------------------------------------------------------------------------- 1 | use mock view models or even a mock session to render different states of the app in a static html document, where we can somehow easily tweak the css (just browser tools, or do something in the page?) how to persist css after changes? 2 | 3 | Also dialogs, forms, ... could be shown on this page. 4 | -------------------------------------------------------------------------------- /scripts/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION=$(jq -r ".version" package.json) 3 | PACKAGE=hydrogen-web-$VERSION.tar.gz 4 | yarn build 5 | pushd target 6 | # move config file so we don't override it 7 | # when deploying a new version 8 | mv config.json config.sample.json 9 | tar -czvf ../$PACKAGE ./ 10 | popd 11 | echo $PACKAGE 12 | -------------------------------------------------------------------------------- /scripts/package-overrides/buffer/index.js: -------------------------------------------------------------------------------- 1 | var Buffer = { 2 | isBuffer: function(array) {return array instanceof Uint8Array;}, 3 | from: function(arrayBuffer) {return arrayBuffer;}, 4 | allocUnsafe: function(size) {return Buffer.alloc(size);}, 5 | alloc: function(size) {return new Uint8Array(size);} 6 | }; 7 | export default Buffer; 8 | -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | FROM docker.io/node:alpine 2 | RUN apk add --no-cache git python3 build-base 3 | 4 | WORKDIR /code 5 | 6 | # Copy package.json and yarn.lock and install dependencies first to speed up subsequent builds 7 | COPY package.json yarn.lock /code/ 8 | RUN yarn install 9 | 10 | COPY . /code 11 | EXPOSE 3000 12 | ENTRYPOINT ["yarn", "start"] 13 | -------------------------------------------------------------------------------- /docker/dynamic-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eux 4 | 5 | if [ -n "${CONFIG_OVERRIDE:-}" ]; then 6 | # Use config override environment variable if set 7 | echo "$CONFIG_OVERRIDE" > /tmp/config.json 8 | else 9 | # Otherwise, use the default config that was bundled in the image 10 | cp /config.json.bundled /tmp/config.json 11 | fi 12 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # Unix-style newlines with a newline ending every file 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | # [*.{js,py}] 16 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/dismiss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/popup.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | .popupContainer { 10 | position: absolute; 11 | white-space: nowrap; 12 | } 13 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export const MIN_UNICODE = "\u{0}"; 10 | export const MAX_UNICODE = "\u{10FFFF}"; 11 | -------------------------------------------------------------------------------- /tsconfig-declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "emitDeclarationOnly": true, 6 | "declaration": true, 7 | "outDir": "target/types", 8 | "rootDir": "src" 9 | }, 10 | "exclude": [ 11 | "src/sdk/paths/*" 12 | ], 13 | "include": ["src/**/*"], 14 | } 15 | -------------------------------------------------------------------------------- /scripts/sdk/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/platform/web/assets/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "push": { 3 | "appId": "io.element.hydrogen.web", 4 | "gatewayUrl": "https://matrix.org", 5 | "applicationServerKey": "BC-gpSdVHEXhvHSHS0AzzWrQoukv2BE7KzpoPO_FfPacqOo3l1pdqz7rSgmB04pZCWaHPz7XRe6fjLaC-WPDopM" 6 | }, 7 | "defaultHomeServer": "matrix.org", 8 | "bugReportEndpointUrl": "https://element.io/bugreports/submit" 9 | } 10 | -------------------------------------------------------------------------------- /doc/architecture/persisted-network-calls.md: -------------------------------------------------------------------------------- 1 | # General Pattern of implementing a persisted network call 2 | 3 | 1. do network request 4 | 1. start transaction 5 | 1. write result of network request into transaction store, keeping differences from previous store state in local variables 6 | 1. close transaction 7 | 1. apply differences applied to store to in-memory data 8 | 1. emit events for changes 9 | -------------------------------------------------------------------------------- /src/utils/error.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class AbortError extends Error { 10 | get name(): string { 11 | return "AbortError"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "noImplicitAny": false, 5 | "noEmit": true, 6 | "target": "ES2020", 7 | "module": "ES2020", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true 10 | }, 11 | "exclude": [ 12 | "src/sdk/paths/*" 13 | ], 14 | "include": ["src/**/*"], 15 | } 16 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - make it a copy, not a fork of brawl, so we can have issues 2 | - add compilation step for ie11 compatible bundle 3 | - compile to es5 4 | - use bluebird for promises 5 | - make xhr request impl 6 | - once app is loading, go over errors 7 | 8 | 9 | - project goals 10 | - works on mobile 11 | - works well offline 12 | - components can be used in isolation 13 | - lazyload components? -------------------------------------------------------------------------------- /doc/problem solving/IMPORT-ISSUES.md: -------------------------------------------------------------------------------- 1 | ## How to import common-js dependency using ES6 syntax 2 | --- 3 | Until [#6632](https://github.com/vitejs/vite/issues/6632) is fixed, such imports should be done as follows: 4 | 5 | ```ts 6 | import * as pkg from "off-color"; 7 | // @ts-ignore 8 | const offColor = pkg.offColor ?? pkg.default.offColor; 9 | ``` 10 | 11 | This way build, dev server and unit tests should all work. 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Docker related files are not maintained by the core Hydrogen team 2 | /.dockerignore @hughns @sandhose 3 | /Dockerfile @hughns @sandhose 4 | /Dockerfile-dev @hughns @sandhose 5 | /.github/workflows/docker-publish.yml @hughns @sandhose 6 | /docker/ @hughns @sandhose 7 | /doc/docker.md @hughns @sandhose 8 | -------------------------------------------------------------------------------- /src/matrix/User.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class User { 10 | constructor(userId) { 11 | this._userId = userId; 12 | } 13 | 14 | get id() { 15 | return this._userId; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/sdk/test/test-sdk-in-esm-vite-build-env.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { build } = require('vite'); 3 | 4 | async function main() { 5 | await build({ 6 | outDir: './dist', 7 | build: { 8 | rollupOptions: { 9 | input: { 10 | main: resolve(__dirname, 'index.html') 11 | } 12 | } 13 | } 14 | }); 15 | 16 | console.log('SDK works in Vite build ✅'); 17 | } 18 | 19 | main(); 20 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/video-call.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /scripts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 2020, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-console": "off", 13 | "no-empty": "off", 14 | "no-prototype-builtins": "off", 15 | "no-unused-vars": "warn" 16 | }, 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/domain/session/leftpanel/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function comparePrimitive(a, b) { 10 | if (a === b) { 11 | return 0; 12 | } else { 13 | return a < b ? -1 : 1; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | if [ -z "$1" ]; then 3 | echo "provide a new version, current version is $(jq '.version' package.json)" 4 | exit 1 5 | fi 6 | VERSION=$1 7 | git checkout master 8 | git pull --rebase origin master 9 | jq ".version = \"$VERSION\"" package.json > package.json.tmp 10 | rm package.json 11 | mv package.json.tmp package.json 12 | git add package.json 13 | git commit -m "release v$VERSION" 14 | git tag "v$VERSION" 15 | git push --tags origin master 16 | -------------------------------------------------------------------------------- /src/utils/enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function createEnum(...values: string[]): Readonly<{}> { 10 | const obj = {}; 11 | for (const value of values) { 12 | obj[value] = value; 13 | } 14 | return Object.freeze(obj); 15 | } 16 | -------------------------------------------------------------------------------- /src/domain/session/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function imageToInfo(image) { 10 | return { 11 | w: image.width, 12 | h: image.height, 13 | mimetype: image.blob.mimeType, 14 | size: image.blob.size 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/platform/web/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hydrogen", 3 | "short_name": "Hydrogen", 4 | "display": "standalone", 5 | "description": "Lightweight matrix client with legacy and mobile browser support", 6 | "start_url": "../index.html", 7 | "icons": [ 8 | {"src": "icon.png", "sizes": "384x384", "type": "image/png"}, 9 | {"src": "icon-maskable.png", "sizes": "384x384", "type": "image/png", "purpose": "maskable"} 10 | ], 11 | "theme_color": "#0DBD8B" 12 | } 13 | -------------------------------------------------------------------------------- /src/platform/web/utils/Base58.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import bs58 from "bs58"; 10 | 11 | export class Base58 { 12 | encode(buffer) { 13 | return bs58.encode(buffer); 14 | } 15 | 16 | decode(str) { 17 | return bs58.decode(str); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /playwright/tests/startup.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {test} from '@playwright/test'; 9 | 10 | test("App has no startup errors that prevent UI render", async ({ page }) => { 11 | await page.goto("/"); 12 | await page.getByText("Log In", { exact: true }).waitFor(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/status.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | .SessionStatusView { 11 | display: flex; 12 | } 13 | 14 | .SessionStatusView p { 15 | margin: 0 10px; 16 | word-break: break-all; 17 | word-break: break-word; 18 | } 19 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export interface IDOMStorage { 10 | getItem(key: string): string | null; 11 | setItem(key: string, value: string): void; 12 | removeItem(key: string): void; 13 | key(n: number): string | null; 14 | readonly length: number; 15 | } 16 | -------------------------------------------------------------------------------- /src/domain/AvatarSource.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020, 2021 The Matrix.org Foundation C.I.C. 4 | Copyright 2020 Bruno Windels 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | export interface AvatarSource { 11 | get avatarLetter(): string; 12 | get avatarColorNumber(): number; 13 | avatarUrl(size: number): string | undefined; 14 | get avatarTitle(): string; 15 | } 16 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/chevron-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/test-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure docker is available 4 | if ! docker --version > /dev/null 2>&1; then 5 | echo "You need to intall docker before you can run the tests!" 6 | exit 1 7 | fi 8 | 9 | # Stop running containers 10 | if docker stop hydrogen-synapse > /dev/null 2>&1; then 11 | echo "Existing 'hydrogen-synapse' container stopped ✔" 12 | fi 13 | 14 | if docker stop hydrogen-dex > /dev/null 2>&1; then 15 | echo "Existing 'hydrogen-dex' container stopped ✔" 16 | fi 17 | 18 | # Run playwright 19 | yarn playwright test 20 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/VerificationCancelledError.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class VerificationCancelledError extends Error { 10 | get name(): string { 11 | return "VerificationCancelledError"; 12 | } 13 | 14 | get message(): string { 15 | return "Verification is cancelled!"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/matrix/ServerFeatures.js: -------------------------------------------------------------------------------- 1 | const R0_5_0 = "r0.5.0"; 2 | 3 | export class ServerFeatures { 4 | constructor(versionResponse) { 5 | this._versionResponse = versionResponse; 6 | } 7 | 8 | _supportsVersion(version) { 9 | if (!this._versionResponse) { 10 | return false; 11 | } 12 | const {versions} = this._versionResponse; 13 | return Array.isArray(versions) && versions.includes(version); 14 | } 15 | 16 | get lazyLoadMembers() { 17 | return this._supportsVersion(R0_5_0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/DisabledComposerView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../general/TemplateView"; 10 | 11 | export class DisabledComposerView extends TemplateView { 12 | render(t) { 13 | return t.div({className: "DisabledComposerView"}, t.h3(vm => vm.description)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/font.css: -------------------------------------------------------------------------------- 1 | /** from https://gist.github.com/mfornos/9991865 */ 2 | 3 | @font-face { 4 | font-family: 'emoji'; 5 | src: local('Apple Color Emoji'), 6 | local('Segoe UI Emoji'), 7 | local('Segoe UI Symbol'), 8 | local('Noto Color Emoji'), 9 | local('Twemoji'), 10 | local('Twemoji Mozilla'), 11 | local('Android Emoji'), 12 | local('EmojiSymbols'), 13 | local('Symbola'); 14 | 15 | /* Emoji unicode blocks */ 16 | unicode-range: U+1F300-1F5FF, U+1F600-1F64F, U+1F680-1F6FF, U+2600-26FF; 17 | } 18 | -------------------------------------------------------------------------------- /src/platform/web/dom/StorageEstimate.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export async function estimateStorageUsage() { 10 | if (navigator?.storage?.estimate) { 11 | const {quota, usage} = await navigator.storage.estimate(); 12 | return {quota, usage}; 13 | } else { 14 | return {quota: null, usage: null}; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/sdk/test/test-sdk-in-commonjs-env.js: -------------------------------------------------------------------------------- 1 | // Make sure the SDK can be used in a CommonJS environment. 2 | // Usage: node scripts/sdk/test/test-sdk-in-commonjs-env.js 3 | const hydrogenViewSdk = require('hydrogen-view-sdk'); 4 | 5 | // Test that the "exports" are available: 6 | // Worker 7 | require.resolve('hydrogen-view-sdk/main.js'); 8 | // Styles 9 | require.resolve('hydrogen-view-sdk/assets/theme-element-light.css'); 10 | // Can access files in the assets/* directory 11 | require.resolve('hydrogen-view-sdk/assets/main.js'); 12 | 13 | console.log('SDK works in CommonJS ✅'); 14 | -------------------------------------------------------------------------------- /src/matrix/login/LoginMethod.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {ILogItem} from "../../logging/types"; 10 | import type {HomeServerApi} from "../net/HomeServerApi.js"; 11 | 12 | export interface ILoginMethod { 13 | homeserver: string; 14 | login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise>; 15 | } 16 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/MissingAttachmentView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageView} from "./BaseMessageView.js"; 10 | 11 | export class MissingAttachmentView extends BaseMessageView { 12 | renderMessageBody(t, vm) { 13 | return t.p({className: "Timeline_messageBody statusMessage"}, vm.label); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/mocks/poll.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export async function poll(fn) { 10 | do { 11 | const result = fn(); 12 | if (result) { 13 | return result; 14 | } else { 15 | await new Promise(setImmediate); //eslint-disable-line no-undef 16 | } 17 | } while (1); //eslint-disable-line no-constant-condition 18 | } -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/paperclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/implementation planning/RELEASE.md: -------------------------------------------------------------------------------- 1 | release: 2 | - bundling css files 3 | - bundling javascript 4 | - run index.html template for release as opposed to develop version? 5 | - make list of all resources needed (images, html page) 6 | - create appcache manifest + service worker 7 | - create tarball + sign 8 | - make gh release with tarball + signature 9 | publish: 10 | - extract tarball 11 | - upload to static website 12 | - overwrite index.html 13 | - overwrite service worker & appcache manifest 14 | - put new version files under /x.x.x 15 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/cam-unmuted.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/platform/web/utils/Encoding.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {UTF8} from "../dom/UTF8.js"; 10 | import {Base64} from "./Base64.js"; 11 | import {Base58} from "./Base58.js"; 12 | 13 | export class Encoding { 14 | constructor() { 15 | this.utf8 = new UTF8(); 16 | this.base64 = new Base64(); 17 | this.base58 = new Base58(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/domain/session/leftpanel/RoomFilter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class RoomFilter { 10 | constructor(query) { 11 | this._parts = query.split(" ").map(s => s.toLowerCase().trim()); 12 | } 13 | 14 | matches(roomTileVM) { 15 | const name = roomTileVM.name.toLowerCase(); 16 | return this._parts.every(p => name.includes(p)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playwright/global-setup.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | const env = { 10 | SYNAPSE_IP_ADDRESS: "172.18.0.5", 11 | SYNAPSE_PORT: "8008", 12 | DEX_IP_ADDRESS: "172.18.0.4", 13 | DEX_PORT: "5556", 14 | } 15 | 16 | export default function setupEnvironmentVariables() { 17 | for (const [key, value] of Object.entries(env)) { 18 | process.env[key] = value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/matrix/storage/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export type Content = { [key: string]: any } 10 | 11 | export interface TimelineEvent { 12 | content: Content; 13 | type: string; 14 | event_id: string; 15 | sender: string; 16 | origin_server_ts: number; 17 | unsigned?: Content; 18 | } 19 | 20 | export type StateEvent = TimelineEvent & { prev_content?: Content, state_key: string } 21 | -------------------------------------------------------------------------------- /src/platform/web/ui/general/LoadingView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {StaticView} from "./StaticView"; 10 | import {spinner} from "../common.js"; 11 | 12 | export class LoadingView extends StaticView { 13 | constructor(label = "Loading") { 14 | super(label, (t, label) => { 15 | return t.div({ className: "LoadingView" }, [spinner(t), label]); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/form.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | .form-row.text > input, .form-row.text > textarea { 11 | display: block; 12 | width: 100%; 13 | min-width: 0; 14 | box-sizing: border-box; 15 | } 16 | 17 | .FilterField { 18 | display: flex; 19 | } 20 | 21 | .FilterField input { 22 | display: block; 23 | flex: 1; 24 | min-width: 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/platform/web/LegacyPlatform.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import aesjs from "aes-js"; 10 | import {hkdf} from "../../utils/crypto/hkdf"; 11 | 12 | import {Platform as ModernPlatform} from "./Platform.js"; 13 | 14 | export function Platform({ container, assetPaths, config, configURL, options = null }) { 15 | return new ModernPlatform({ container, assetPaths, config, configURL, options, cryptoExtras: { aesjs, hkdf }}); 16 | } 17 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/implementation planning/session-container.md: -------------------------------------------------------------------------------- 1 | what should this new container be called? 2 | - Client 3 | - SessionContainer 4 | 5 | 6 | it is what is returned from bootstrapping a ... thing 7 | it allows you to replace classes within the client through IoC? 8 | it wires up the different components 9 | it unwires the components when you're done with the thing 10 | it could hold all the dependencies for setting up a client, even before login 11 | - online detection api 12 | - clock 13 | - homeserver 14 | - requestFn 15 | 16 | we'll be explicitly making its parts public though, like session, sync, reconnector 17 | 18 | merge the connectionstate and 19 | -------------------------------------------------------------------------------- /codestyle.md: -------------------------------------------------------------------------------- 1 | 2 | # Code-style 3 | 4 | - methods that return a promise should always use async/await 5 | otherwise synchronous errors can get swallowed 6 | you can return a promise without awaiting it though. 7 | - only named exports, no default exports 8 | otherwise it becomes hard to remember what was a default/named export 9 | - should we return promises from storage mutation calls? probably not, as we don't await them anywhere. only read calls should return promises? 10 | - we don't anymore 11 | - don't use these features, as they are not widely enough supported. 12 | - [lookbehind in regular expressions](https://caniuse.com/js-regexp-lookbehind) 13 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/disable-grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/stages/constants.ts: -------------------------------------------------------------------------------- 1 | // From element-web 2 | export type KeyAgreement = "curve25519-hkdf-sha256" | "curve25519"; 3 | export type MacMethod = "hkdf-hmac-sha256.v2" | "org.matrix.msc3783.hkdf-hmac-sha256" | "hkdf-hmac-sha256" | "hmac-sha256"; 4 | 5 | export const KEY_AGREEMENT_LIST: KeyAgreement[] = ["curve25519-hkdf-sha256", "curve25519"]; 6 | export const HASHES_LIST = ["sha256"]; 7 | export const MAC_LIST: MacMethod[] = [ 8 | "hkdf-hmac-sha256.v2", 9 | "org.matrix.msc3783.hkdf-hmac-sha256", 10 | "hkdf-hmac-sha256", 11 | "hmac-sha256", 12 | ]; 13 | export const SAS_LIST = ["decimal", "emoji"]; 14 | export const SAS_SET = new Set(SAS_LIST); 15 | -------------------------------------------------------------------------------- /src/platform/web/sdk/paths/vite.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import _downloadSandboxPath from "../../assets/download-sandbox.html?url"; 3 | // @ts-ignore 4 | import _workerPath from "../../worker/main.js?url"; 5 | // @ts-ignore 6 | import olmWasmPath from "@matrix-org/olm/olm.wasm?url"; 7 | // @ts-ignore 8 | import olmJsPath from "@matrix-org/olm/olm.js?url"; 9 | // @ts-ignore 10 | import olmLegacyJsPath from "@matrix-org/olm/olm_legacy.js?url"; 11 | 12 | export default { 13 | downloadSandbox: _downloadSandboxPath, 14 | worker: _workerPath, 15 | olm: { 16 | wasm: olmWasmPath, 17 | legacyBundle: olmLegacyJsPath, 18 | wasmBundle: olmJsPath, 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from "@playwright/test"; 2 | 3 | const BASE_URL = process.env["BASE_URL"] ?? "http://127.0.0.1:3000"; 4 | 5 | const config: PlaywrightTestConfig = { 6 | use: { 7 | headless: false, 8 | viewport: { width: 1280, height: 720 }, 9 | ignoreHTTPSErrors: true, 10 | video: "on-first-retry", 11 | baseURL: BASE_URL, 12 | }, 13 | testDir: "./playwright/tests", 14 | globalSetup: require.resolve("./playwright/global-setup"), 15 | webServer: { 16 | command: "yarn start", 17 | url: `${BASE_URL}/#/login`, 18 | }, 19 | workers: 1 20 | }; 21 | export default config; 22 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/RoomNameTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {SimpleTile} from "./SimpleTile"; 10 | 11 | export class RoomNameTile extends SimpleTile { 12 | 13 | get shape() { 14 | return "announcement"; 15 | } 16 | 17 | get announcement() { 18 | const content = this._entry.content; 19 | return `${this._entry.displayName || this._entry.sender} named the room "${content?.name}"` 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/domain/session/verification/stages/MissingKeysViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ViewModel, Options} from "../../../ViewModel"; 10 | import type {SegmentType} from "../../../navigation/index"; 11 | 12 | export class MissingKeysViewModel extends ViewModel { 13 | gotoSettings() { 14 | this.navigation.push("settings", true); 15 | } 16 | 17 | get kind(): string { 18 | return "keys-missing"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /prototypes/tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.11.1", 13 | "@babel/preset-env": "^7.11.0", 14 | "@rollup/plugin-babel": "^5.1.0", 15 | "@rollup/plugin-commonjs": "^15.0.0", 16 | "@rollup/plugin-multi-entry": "^4.0.0", 17 | "@rollup/plugin-node-resolve": "^9.0.0", 18 | "mdn-polyfills": "^5.20.0", 19 | "regenerator-runtime": "^0.13.7", 20 | "rollup": "^2.26.4", 21 | "core-js": "^3.6.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/EncryptionEnabledTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {SimpleTile} from "./SimpleTile"; 10 | 11 | export class EncryptionEnabledTile extends SimpleTile { 12 | get shape() { 13 | return "announcement"; 14 | } 15 | 16 | get announcement() { 17 | const senderName = this._entry.displayName || this._entry.sender; 18 | return this.i18n`${senderName} has enabled end-to-end encryption`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/observable/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | 10 | // re-export "root" (of chain) collection 11 | export { ObservableMap, ApplyMap, FilteredMap, JoinedMap, LogMap, MappedMap } from "./map"; 12 | export { ObservableArray } from "./list/ObservableArray"; 13 | export { SortedArray } from "./list/SortedArray"; 14 | export { MappedList } from "./list/MappedList"; 15 | export { AsyncMappedList } from "./list/AsyncMappedList"; 16 | export { ConcatList } from "./list/ConcatList"; 17 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/e2ee-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/matrix/login/SSOLoginHelper.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class SSOLoginHelper{ 10 | private _homeserver: string; 11 | 12 | constructor(homeserver: string) { 13 | this._homeserver = homeserver; 14 | } 15 | 16 | get homeserver(): string { return this._homeserver; } 17 | 18 | createSSORedirectURL(returnURL: string): string { 19 | return `${this._homeserver}/_matrix/client/r0/login/sso/redirect?redirectUrl=${encodeURIComponent(returnURL)}`; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/verification-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/TimelineLoadingView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../general/TemplateView"; 10 | import {spinner} from "../../common.js"; 11 | 12 | export class TimelineLoadingView extends TemplateView { 13 | render(t, vm) { 14 | return t.div({className: "TimelineLoadingView"}, [ 15 | spinner(t), 16 | t.div(vm.isEncrypted ? vm.i18n`Loading encrypted messages…` : vm.i18n`Loading messages…`) 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /playwright/plugins/synapsedocker/templates/consent/res/templates/privacy/en/1.0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Privacy policy 5 | 6 | 7 | {% if has_consented %} 8 |

9 | Thank you, you've already accepted the license. 10 |

11 | {% else %} 12 |

13 | Please accept the license! 14 |

15 |
16 | 17 | 18 | 19 | 20 |
21 | {% endif %} 22 | 23 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/vertical-ellipsis.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/matrix/registration/stages/DummyAuth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {AuthenticationData} from "../types"; 10 | import {BaseRegistrationStage} from "./BaseRegistrationStage"; 11 | 12 | export class DummyAuth extends BaseRegistrationStage { 13 | generateAuthenticationData(): AuthenticationData { 14 | return { 15 | session: this._session, 16 | type: this.type, 17 | }; 18 | } 19 | 20 | get type(): string { 21 | return "m.login.dummy"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import type {IChannel} from "./channel/IChannel"; 9 | import type {CalculateSASStage} from "./stages/CalculateSASStage"; 10 | import type {SelectVerificationMethodStage} from "./stages/SelectVerificationMethodStage"; 11 | 12 | export type SASProgressEvents = { 13 | SelectVerificationStage: SelectVerificationMethodStage; 14 | EmojiGenerated: CalculateSASStage; 15 | VerificationCompleted: string; 16 | VerificationCancelled: IChannel["cancellation"]; 17 | } 18 | -------------------------------------------------------------------------------- /src/platform/web/theming/parsers/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export type NormalVariant = { 10 | id: string; 11 | cssLocation: string; 12 | variables?: any; 13 | }; 14 | 15 | export type Variant = NormalVariant & { 16 | variantName: string; 17 | }; 18 | 19 | export type DefaultVariant = { 20 | dark: Variant; 21 | light: Variant; 22 | default: Variant; 23 | } 24 | 25 | export type ThemeInformation = NormalVariant | DefaultVariant; 26 | 27 | export enum ColorSchemePreference { 28 | Dark, 29 | Light 30 | }; 31 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/LocationView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageView} from "./BaseMessageView.js"; 10 | 11 | export class LocationView extends BaseMessageView { 12 | renderMessageBody(t, vm) { 13 | return t.p({className: "Timeline_messageBody statusMessage"}, [ 14 | t.span(vm.label), 15 | t.a({className: "Timeline_locationLink", href: vm.mapsLink, target: "_blank", rel: "noopener"}, vm.i18n`Open in maps`), 16 | t.time(vm.time) 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /scripts/postcss/tests/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | const postcss = require("postcss"); 10 | 11 | module.exports.createTestRunner = function (plugin) { 12 | return async function run(input, output, opts = {}, assert) { 13 | let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); 14 | assert.strictEqual( 15 | result.css.replaceAll(/\s/g, ""), 16 | output.replaceAll(/\s/g, "") 17 | ); 18 | assert.strictEqual(result.warnings().length, 0); 19 | }; 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /doc/implementation planning/background-tasks.md: -------------------------------------------------------------------------------- 1 | we make the current session status bar float and display generally short messages for all background tasks like: 2 | "Waiting Xs to reconnect... [try now]" 3 | "Reconnecting..." 4 | "Sending message 1 of 10..." 5 | 6 | As it is floating, it doesn't pop they layout and mess up the scroll offset of the timeline. 7 | Need to find a good place to float it though. Preferably on top for visibility, but it could occlude the room header. Perhaps bottom left? 8 | 9 | If more than 1 background thing is going on at the same time we display (1/x). 10 | If you click the button status bar anywhere, it takes you to a page adjacent to the room view (and e.g. in the future the settings) and you get an overview of all running background tasks. 11 | -------------------------------------------------------------------------------- /prototypes/ie11-hashchange.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
    8 | 18 |

    19 | foo 20 | bar 21 | baz 22 |

    23 | 24 | 25 | -------------------------------------------------------------------------------- /src/platform/web/dom/UTF8.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | export class UTF8 { 9 | constructor() { 10 | this._encoder = null; 11 | this._decoder = null; 12 | } 13 | 14 | encode(str) { 15 | if (!this._encoder) { 16 | this._encoder = new TextEncoder(); 17 | } 18 | return this._encoder.encode(str); 19 | } 20 | 21 | decode(buffer) { 22 | if (!this._decoder) { 23 | this._decoder = new TextDecoder(); 24 | } 25 | return this._decoder.decode(buffer); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/RetainedValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class RetainedValue { 10 | private readonly _freeCallback: () => void; 11 | private _retentionCount: number = 1; 12 | 13 | constructor(freeCallback: () => void) { 14 | this._freeCallback = freeCallback; 15 | } 16 | 17 | retain(): void { 18 | this._retentionCount += 1; 19 | } 20 | 21 | release(): void { 22 | this._retentionCount -= 1; 23 | if (this._retentionCount === 0) { 24 | this._freeCallback(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/ImageView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMediaView} from "./BaseMediaView.js"; 10 | 11 | export class ImageView extends BaseMediaView { 12 | renderMedia(t, vm) { 13 | const img = t.img({ 14 | src: vm => vm.thumbnailUrl, 15 | alt: vm => vm.label, 16 | title: vm => vm.label, 17 | style: `max-width: ${vm.width}px; max-height: ${vm.height}px;` 18 | }); 19 | return vm.isPending || !vm.lightboxUrl ? img : t.a({href: vm.lightboxUrl}, img); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/observable/value/RetainedObservableValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ObservableValue} from "./index"; 10 | 11 | export class RetainedObservableValue extends ObservableValue { 12 | 13 | constructor(initialValue: T, private freeCallback: () => void, private startCallback: () => void = () => {}) { 14 | super(initialValue); 15 | } 16 | 17 | onSubscribeFirst(): void { 18 | this.startCallback(); 19 | } 20 | 21 | onUnsubscribeLast(): void { 22 | super.onUnsubscribeLast(); 23 | this.freeCallback(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/rightpanel/MemberTileView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../general/TemplateView"; 10 | import {AvatarView} from "../../AvatarView.js"; 11 | 12 | export class MemberTileView extends TemplateView { 13 | render(t, vm) { 14 | return t.li({ className: "MemberTileView" }, 15 | t.a({ href: vm.detailsUrl }, 16 | [ 17 | t.view(new AvatarView(vm, 32)), 18 | t.div({ className: "MemberTileView_name" }, (vm) => vm.name), 19 | ]) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/platform/web/theming/shared/svg-colorizer.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function getColoredSvgString(svgString, primaryColor, secondaryColor) { 10 | let coloredSVGCode = svgString.replaceAll("#ff00ff", primaryColor); 11 | coloredSVGCode = coloredSVGCode.replaceAll("#00ffff", secondaryColor); 12 | if (svgString === coloredSVGCode) { 13 | throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); 14 | } 15 | return coloredSVGCode; 16 | } 17 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/DateHeaderView.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../../general/TemplateView"; 10 | import type {DateTile} from "../../../../../../domain/session/room/timeline/tiles/DateTile"; 11 | 12 | export class DateHeaderView extends TemplateView { 13 | render(t, vm) { 14 | return t.h2({className: "DateHeader"}, t.time({dateTime: vm.machineReadableDate}, vm.relativeDate)); 15 | } 16 | 17 | /* This is called by the parent ListView, which just has 1 listener for the whole list */ 18 | onClick() {} 19 | } 20 | -------------------------------------------------------------------------------- /src/platform/web/utils/Base64.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import base64 from "base64-arraybuffer"; 10 | 11 | export class Base64 { 12 | encodeUnpadded(buffer) { 13 | const str = base64.encode(buffer); 14 | const paddingIdx = str.indexOf("="); 15 | if (paddingIdx !== -1) { 16 | return str.substr(0, paddingIdx); 17 | } else { 18 | return str; 19 | } 20 | } 21 | 22 | encode(buffer) { 23 | return base64.encode(buffer); 24 | } 25 | 26 | decode(str) { 27 | return base64.decode(str); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/sdk/create-manifest.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require("fs"); 3 | const appManifest = require("../../package.json"); 4 | const baseSDKManifest = require("./base-manifest.json"); 5 | /* 6 | Need to leave typescript type definitions out until the 7 | typescript conversion is complete and all imports in the d.ts files 8 | exists. 9 | ``` 10 | "types": "types/lib.d.ts" 11 | ``` 12 | */ 13 | const mergeOptions = require('merge-options'); 14 | 15 | const manifestExtension = { 16 | devDependencies: undefined, 17 | scripts: undefined, 18 | }; 19 | 20 | const manifest = mergeOptions(appManifest, baseSDKManifest, manifestExtension); 21 | const json = JSON.stringify(manifest, undefined, 2); 22 | const outFile = process.argv[2]; 23 | fs.writeFileSync(outFile, json, {encoding: "utf8"}); 24 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/sdk/test/esm-entry.ts: -------------------------------------------------------------------------------- 1 | import * as hydrogenViewSdk from "hydrogen-view-sdk"; 2 | import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url'; 3 | import workerPath from 'hydrogen-view-sdk/main.js?url'; 4 | import olmWasmPath from '@matrix-org/olm/olm.wasm?url'; 5 | import olmJsPath from '@matrix-org/olm/olm.js?url'; 6 | import olmLegacyJsPath from '@matrix-org/olm/olm_legacy.js?url'; 7 | const assetPaths = { 8 | downloadSandbox: downloadSandboxPath, 9 | worker: workerPath, 10 | olm: { 11 | wasm: olmWasmPath, 12 | legacyBundle: olmLegacyJsPath, 13 | wasmBundle: olmJsPath 14 | } 15 | }; 16 | import "hydrogen-view-sdk/assets/theme-element-light.css"; 17 | 18 | console.log('hydrogenViewSdk', hydrogenViewSdk); 19 | console.log('assetPaths', assetPaths); 20 | 21 | console.log('Entry ESM works ✅'); 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: "eslint:recommended", 7 | parserOptions: { 8 | ecmaVersion: 2020, 9 | sourceType: "module", 10 | }, 11 | rules: { 12 | "no-console": "off", 13 | "no-empty": "off", 14 | "no-prototype-builtins": "off", 15 | "no-unused-vars": "warn", 16 | }, 17 | globals: { 18 | DEFINE_VERSION: "readonly", 19 | DEFINE_GLOBAL_HASH: "readonly", 20 | DEFINE_IS_SDK: "readonly", 21 | DEFINE_PROJECT_DIR: "readonly", 22 | // only available in sw.js 23 | DEFINE_UNHASHED_PRECACHED_ASSETS: "readonly", 24 | DEFINE_HASHED_PRECACHED_ASSETS: "readonly", 25 | DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS: "readonly", 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/AnnouncementView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../../general/TemplateView"; 10 | 11 | export class AnnouncementView extends TemplateView { 12 | // ignore other arguments 13 | constructor(vm) { 14 | super(vm); 15 | } 16 | 17 | render(t, vm) { 18 | return t.li({ 19 | className: "AnnouncementView", 20 | 'data-event-id': vm.eventId 21 | }, t.div(vm => vm.announcement)); 22 | } 23 | 24 | /* This is called by the parent ListView, which just has 1 listener for the whole list */ 25 | onClick() {} 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/formatSize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | 10 | export function formatSize(size: number, decimals: number = 2): string { 11 | if (Number.isSafeInteger(size)) { 12 | const base = Math.min(3, Math.floor(Math.log(size) / Math.log(1024))); 13 | const formattedSize = Math.round(size / Math.pow(1024, base)).toFixed(decimals); 14 | switch (base) { 15 | case 0: return `${formattedSize} bytes`; 16 | case 1: return `${formattedSize} KB`; 17 | case 2: return `${formattedSize} MB`; 18 | case 3: return `${formattedSize} GB`; 19 | } 20 | } 21 | return ""; 22 | } 23 | -------------------------------------------------------------------------------- /prototypes/base256.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/platform/web/ui/general/StaticView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | import {tag} from "../general/html"; 11 | 12 | export class StaticView { 13 | constructor(value, render = undefined) { 14 | if (typeof value === "function" && !render) { 15 | render = value; 16 | value = null; 17 | } 18 | this._root = render ? render(tag, value) : this.render(tag, value); 19 | } 20 | 21 | mount() { 22 | return this._root; 23 | } 24 | 25 | root() { 26 | return this._root; 27 | } 28 | 29 | unmount() {} 30 | update() {} 31 | } 32 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/stages/SendDoneStage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {BaseSASVerificationStage} from "./BaseSASVerificationStage"; 9 | import {VerificationEventType} from "../channel/types"; 10 | 11 | export class SendDoneStage extends BaseSASVerificationStage { 12 | async completeStage() { 13 | await this.log.wrap("SendDoneStage.completeStage", async (log) => { 14 | await this.channel.send(VerificationEventType.Done, {}, log); 15 | await this.channel.waitForEvent(VerificationEventType.Done); 16 | this.eventEmitter.emit("VerificationCompleted", this.otherUserDeviceId); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/FileView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageView} from "./BaseMessageView.js"; 10 | 11 | export class FileView extends BaseMessageView { 12 | renderMessageBody(t, vm) { 13 | const children = []; 14 | if (vm.isPending) { 15 | children.push(vm => vm.label); 16 | } else { 17 | children.push( 18 | t.button({className: "link", onClick: () => vm.download()}, vm => vm.label), 19 | t.time(vm.time) 20 | ); 21 | } 22 | return t.p({className: "Timeline_messageBody statusMessage"}, children); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/RedactedView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageView} from "./BaseMessageView.js"; 10 | import {Menu} from "../../../general/Menu.js"; 11 | 12 | export class RedactedView extends BaseMessageView { 13 | renderMessageBody(t) { 14 | return t.p({className: "Timeline_messageBody statusMessage"}, vm => vm.description); 15 | } 16 | 17 | createMenuOptions(vm) { 18 | const options = super.createMenuOptions(vm); 19 | if (vm.isRedacting) { 20 | options.push(Menu.option(vm.i18n`Cancel`, () => vm.abortPendingRedaction())); 21 | } 22 | return options; 23 | } 24 | } -------------------------------------------------------------------------------- /scripts/sdk/base-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hydrogen-view-sdk", 3 | "description": "Embeddable matrix client library, including view components", 4 | "version": "0.3.1", 5 | "main": "./lib-build/hydrogen.cjs.js", 6 | "exports": { 7 | ".": { 8 | "import": "./lib-build/hydrogen.es.js", 9 | "require": "./lib-build/hydrogen.cjs.js" 10 | }, 11 | "./paths/vite": "./paths/vite.js", 12 | "./style.css": "./asset-build/assets/theme-element-light.css", 13 | "./theme-element-light.css": "./asset-build/assets/theme-element-light.css", 14 | "./theme-element-dark.css": "./asset-build/assets/theme-element-dark.css", 15 | "./main.js": "./asset-build/assets/main.js", 16 | "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", 17 | "./assets/*": "./asset-build/assets/*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /doc/implementation planning/CATCHUP-BACKFILL.md: -------------------------------------------------------------------------------- 1 | we should automatically fill gaps (capped at a certain (large) amount of events, 5000?) after a limited sync for a room 2 | 3 | ## E2EE rooms 4 | 5 | during these fills (once supported), we should calculate push actions and trigger notifications, as we would otherwise have received this through sync. 6 | 7 | we could also trigger notifications when just backfilling on initial sync up to a certain amount of time in the past? 8 | 9 | 10 | we also need to backfill if we didn't receive any m.room.message in a limited sync for an encrypted room, as it's possible the room summary hasn't seen the last message in the room and is now out of date. this is also true for a non-encrypted room actually, although wrt to the above, here notifications would work well though. 11 | 12 | a room should request backfills in needsAfterSyncCompleted and do them in afterSyncCompleted. 13 | -------------------------------------------------------------------------------- /prototypes/online.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
      8 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/UserIdentityStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {Store} from "../Store"; 9 | import type {UserIdentity} from "../../../e2ee/DeviceTracker"; 10 | 11 | export class UserIdentityStore { 12 | private _store: Store; 13 | 14 | constructor(store: Store) { 15 | this._store = store; 16 | } 17 | 18 | get(userId: string): Promise { 19 | return this._store.get(userId); 20 | } 21 | 22 | set(userIdentity: UserIdentity): void { 23 | this._store.put(userIdentity); 24 | } 25 | 26 | remove(userId: string): void { 27 | this._store.delete(userId); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /prototypes/ie11-textdecoder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/matrix/e2ee/megolm/decryption/README.md: -------------------------------------------------------------------------------- 1 | Lots of classes here. The complexity comes from needing to offload decryption to a webworker, mainly for IE11. We can't keep a idb transaction open while waiting for the response from the worker, so need to batch decryption of multiple events and do decryption in multiple steps: 2 | 3 | 1. Read all used inbound sessions for the batch of events, requires a read txn. This happens in `Decryption`. Sessions are loaded into `SessionInfo` objects, which are also kept in a `SessionCache` to prevent having to read and unpickle them all the time. 4 | 2. Actually decrypt. No txn can stay open during this step, as it can be offloaded to a worker and is thus async. This happens in `DecryptionPreparation`, which delegates to `SessionDecryption` per session. 5 | 3. Read and write for the replay detection, requires a read/write txn. This happens in `DecryptionChanges` 6 | 4. Return the decrypted entries, and errors if any 7 | -------------------------------------------------------------------------------- /.ts-eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | "browser": true, 5 | "es6": true 6 | }, 7 | extends: [ 8 | // "plugin:@typescript-eslint/recommended", 9 | // "plugin:@typescript-eslint/recommended-requiring-type-checking", 10 | ], 11 | parser: '@typescript-eslint/parser', 12 | parserOptions: { 13 | "ecmaVersion": 2020, 14 | "sourceType": "module", 15 | "project": "./tsconfig.json" 16 | }, 17 | plugins: [ 18 | '@typescript-eslint', 19 | ], 20 | rules: { 21 | "@typescript-eslint/no-floating-promises": 2, 22 | "@typescript-eslint/no-misused-promises": 2, 23 | "no-unused-vars": "off", 24 | "@typescript-eslint/no-unused-vars": ["warn"], 25 | "no-undef": "off", 26 | "semi": ["error", "always"], 27 | "@typescript-eslint/explicit-function-return-type": ["error"] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/platform/web/ui/general/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | Copyright 2021 Daniel Fedorin 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | export interface IMountArgs { 10 | // if true, the parent will call update() rather than the view updating itself by binding to a data source. 11 | parentProvidesUpdates?: boolean 12 | }; 13 | 14 | // Comment nodes can be used as temporary placeholders for Elements, like TemplateView does. 15 | export type ViewNode = Element | Comment; 16 | 17 | export interface IView { 18 | mount(args?: IMountArgs): ViewNode; 19 | root(): ViewNode | undefined; // should only be called between mount() and unmount() 20 | unmount(): void; 21 | update(...any); // this isn't really standarized yet 22 | } 23 | -------------------------------------------------------------------------------- /src/domain/session/room/README.md: -------------------------------------------------------------------------------- 1 | # "Room" view models 2 | 3 | InviteViewModel, RoomViewModel and RoomBeingCreatedViewModel are interchangebly used as "room view model": 4 | - SessionViewModel.roomViewModel can be an instance of any 5 | - RoomGridViewModel.roomViewModelAt(i) can return an instance of any 6 | 7 | This is because they are accessed by the same url and need to transition into each other, in these two locations. Having two methods, especially in RoomGridViewModel would have been more cumbersome, even though this is not in line with how different view models are exposed in SessionViewModel. 8 | 9 | They share an `id` and `kind` property, the latter can be used to differentiate them from the view, and a `focus` method. 10 | Once we convert this folder to typescript, we should use this interface for all the view models: 11 | ```ts 12 | interface IGridItemViewModel { 13 | id: string; 14 | kind: string; 15 | focus(); 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/MissingAttachmentTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageTile} from "./BaseMessageTile.js"; 10 | 11 | export class MissingAttachmentTile extends BaseMessageTile { 12 | get shape() { 13 | return "missing-attachment" 14 | } 15 | 16 | get label() { 17 | const name = this._getContent().body; 18 | const msgtype = this._getContent().msgtype; 19 | if (msgtype === "m.image") { 20 | return this.i18n`The image ${name} wasn't fully sent previously and could not be recovered.`; 21 | } else { 22 | return this.i18n`The file ${name} wasn't fully sent previously and could not be recovered.`; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/platform/web/ui/login/SessionLoadView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../general/TemplateView"; 10 | import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; 11 | 12 | export class SessionLoadView extends TemplateView { 13 | render(t, vm) { 14 | return t.div({className: "PreSessionScreen"}, [ 15 | t.div({className: "logo"}), 16 | t.div({className: "SessionLoadView"}, [ 17 | t.view(new SessionLoadStatusView(vm)) 18 | ]), 19 | t.div({className: {"button-row": true, hidden: vm => vm.loading}}, 20 | t.a({className: "button-action primary", href: vm.backUrl}, vm.i18n`Go back`)) 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/matrix/e2ee/megolm/decryption/ReplayDetectionEntry.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {TimelineEvent} from "../../../storage/types"; 10 | 11 | export class ReplayDetectionEntry { 12 | public readonly sessionId: string; 13 | public readonly messageIndex: number; 14 | public readonly event: TimelineEvent; 15 | 16 | constructor(sessionId: string, messageIndex: number, event: TimelineEvent) { 17 | this.sessionId = sessionId; 18 | this.messageIndex = messageIndex; 19 | this.event = event; 20 | } 21 | 22 | get eventId(): string { 23 | return this.event.event_id; 24 | } 25 | 26 | get timestamp(): number { 27 | return this.event.origin_server_ts; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/matrix/room/timeline/persistence/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function createEventEntry(key, roomId, event) { 10 | return { 11 | fragmentId: key.fragmentId, 12 | eventIndex: key.eventIndex, 13 | roomId, 14 | event: event, 15 | }; 16 | } 17 | 18 | export function directionalAppend(array, value, direction) { 19 | if (direction.isForward) { 20 | array.push(value); 21 | } else { 22 | array.unshift(value); 23 | } 24 | } 25 | 26 | export function directionalConcat(array, otherArray, direction) { 27 | if (direction.isForward) { 28 | return array.concat(otherArray); 29 | } else { 30 | return otherArray.concat(array); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/cam-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/matrix/room/timeline/Direction.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class Direction { 10 | constructor(public readonly isForward: boolean) { 11 | } 12 | 13 | get isBackward(): boolean { 14 | return !this.isForward; 15 | } 16 | 17 | asApiString(): string { 18 | return this.isForward ? "f" : "b"; 19 | } 20 | 21 | reverse(): Direction { 22 | return this.isForward ? Direction.Backward : Direction.Forward 23 | } 24 | 25 | static get Forward(): Direction { 26 | return _forward; 27 | } 28 | 29 | static get Backward(): Direction { 30 | return _backward; 31 | } 32 | } 33 | 34 | const _forward = new Direction(true); 35 | const _backward = new Direction(false); 36 | -------------------------------------------------------------------------------- /src/platform/web/ui/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | let container; 10 | 11 | export function spinner(t, extraClasses = undefined) { 12 | if (container === undefined) { 13 | container = document.querySelector(".hydrogen"); 14 | } 15 | const classes = Object.assign({"spinner": true}, extraClasses); 16 | if (container?.classList.contains("legacy")) { 17 | return t.div({className: classes}, [ 18 | t.div(), 19 | t.div(), 20 | t.div(), 21 | t.div(), 22 | ]); 23 | } else { 24 | return t.svg({className: classes, viewBox:"0 0 100 100"}, 25 | t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"}) 26 | ); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /doc/implementation planning/LOCAL-ECHO-STATE.md: -------------------------------------------------------------------------------- 1 | # Local echo 2 | 3 | ## Remote vs local state for account_data, etc ... 4 | 5 | For things like account data, and other requests that might fail, we could persist what we are sending next to the last remote version we have (with a flag for which one is remote and local, part of the key). E.g. for account data the key would be: [type, localOrRemoteFlag] 6 | 7 | localOrRemoteFlag would be 1 of 3: 8 | - Remote 9 | - (Local)Unsent 10 | - (Local)Sent 11 | 12 | although we only want 1 remote and 1 local value for a given key, perhaps a second field where localOrRemoteFlag is a boolean, and a sent=boolean field as well? We need this to know if we need to retry. 13 | 14 | This will allow resending of these requests if needed. Once the request goes through, we remove the local version. 15 | 16 | then we can also see what the current value is with or without the pending local changes, and we don't have to wait for remote echo... 17 | -------------------------------------------------------------------------------- /src/platform/web/ui/login/CompleteSSOView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../general/TemplateView"; 10 | import {SessionLoadStatusView} from "./SessionLoadStatusView.js"; 11 | 12 | export class CompleteSSOView extends TemplateView { 13 | render(t) { 14 | return t.div({ className: "CompleteSSOView" }, 15 | [ 16 | t.p({ className: "CompleteSSOView_title" }, "Finishing up your SSO Login"), 17 | t.if(vm => vm.errorMessage, (t, vm) => t.p({className: "error"}, vm.i18n(vm.errorMessage))), 18 | t.mapView(vm => vm.loadViewModel, loadViewModel => loadViewModel ? new SessionLoadStatusView(loadViewModel) : null), 19 | ] 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/Deferred.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class Deferred { 10 | public readonly promise: Promise; 11 | public readonly resolve: (value: T) => void; 12 | public readonly reject: (err: Error) => void; 13 | private _value?: T; 14 | 15 | constructor() { 16 | let resolve; 17 | let reject; 18 | this.promise = new Promise((_resolve, _reject) => { 19 | resolve = _resolve; 20 | reject = _reject; 21 | }) 22 | this.resolve = (value: T) => { 23 | this._value = value; 24 | resolve(value); 25 | }; 26 | this.reject = reject; 27 | } 28 | 29 | get value(): T | undefined { 30 | return this._value; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/platform/web/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/logging/utils.ts: -------------------------------------------------------------------------------- 1 | // these are helper functions if you can't assume you always have a log item (e.g. some code paths call with one set, others don't) 2 | // if you know you always have a log item, better to use the methods on the log item than these utility functions. 3 | 4 | import {Instance as NullLoggerInstance} from "./NullLogger"; 5 | import type {FilterCreator, ILogItem, LabelOrValues, LogCallback} from "./types"; 6 | import {LogLevel} from "./LogFilter"; 7 | 8 | export function wrapOrRunNullLogger(logItem: ILogItem | undefined, labelOrValues: LabelOrValues, callback: LogCallback, logLevel?: LogLevel, filterCreator?: FilterCreator): T | Promise { 9 | if (logItem) { 10 | return logItem.wrap(labelOrValues, callback, logLevel, filterCreator); 11 | } else { 12 | return NullLoggerInstance.run(null, callback); 13 | } 14 | } 15 | 16 | export function ensureLogItem(logItem: ILogItem): ILogItem { 17 | return logItem || NullLoggerInstance.item; 18 | } 19 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/OutboundGroupSessionStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {Store} from "../Store"; 9 | 10 | interface OutboundSession { 11 | roomId: string; 12 | session: string; 13 | createdAt: number; 14 | } 15 | 16 | export class OutboundGroupSessionStore { 17 | private _store: Store; 18 | 19 | constructor(store: Store) { 20 | this._store = store; 21 | } 22 | 23 | remove(roomId: string): void { 24 | this._store.delete(roomId); 25 | } 26 | 27 | get(roomId: string): Promise { 28 | return this._store.get(roomId); 29 | } 30 | 31 | set(session: OutboundSession): void { 32 | this._store.put(session); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/SharedSecretStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {Store} from "../Store"; 9 | 10 | type SharedSecret = any; 11 | 12 | export class SharedSecretStore { 13 | private _store: Store; 14 | 15 | constructor(store: Store) { 16 | this._store = store; 17 | } 18 | 19 | get(name: string): Promise { 20 | return this._store.get(name); 21 | } 22 | 23 | set(name: string, secret: SharedSecret): void { 24 | secret.key = name; 25 | this._store.put(secret); 26 | } 27 | 28 | remove(name: string): void { 29 | this._store.delete(name); 30 | } 31 | 32 | deleteAllSecrets(): void { 33 | this._store.clear(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /doc/implementation planning/ROOM-VERSIONS.md: -------------------------------------------------------------------------------- 1 | - add internal room ids (to support room versioning later, and make internal event ids smaller and not needing escaping, and not needing a migration later on) ... hm this might need some more though. how to address a logical room? last room id? also we might not need it for room versioning ... it would basically be to make the ids smaller, but as idb is compressing, not sure that's a good reason? Although as we keep all room summaries in memory, it would be easy to map between these... you'd get event ids like 0000E78A00000020000A0B3C with room id, fragment id and event index. The room summary would store: 2 | ``` 3 | rooms: { 4 | "!eKhOsgLidcrWMWnxOr:vector.modular.im": 0x0000E78A, 5 | ... 6 | } 7 | mostRecentRoom: 0x0000E78A 8 | ``` 9 | if this is not on an indexed field, how can we do a query to find the last room id and +1 to assign a new one? 10 | 11 | how do we identify a logical room (consisting on a recent room and perhaps multiple outdated ones)? 12 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/channel/types.ts: -------------------------------------------------------------------------------- 1 | export const enum VerificationEventType { 2 | Request = "m.key.verification.request", 3 | Ready = "m.key.verification.ready", 4 | Start = "m.key.verification.start", 5 | Accept = "m.key.verification.accept", 6 | Key = "m.key.verification.key", 7 | Cancel = "m.key.verification.cancel", 8 | Mac = "m.key.verification.mac", 9 | Done = "m.key.verification.done", 10 | } 11 | 12 | export const enum CancelReason { 13 | UserCancelled = "m.user", 14 | TimedOut = "m.timeout", 15 | UnknownTransaction = "m.unknown_transaction", 16 | UnknownMethod = "m.unknown_method", 17 | UnexpectedMessage = "m.unexpected_message", 18 | KeyMismatch = "m.key_mismatch", 19 | UserMismatch = "m.user_mismatch", 20 | InvalidMessage = "m.invalid_message", 21 | OtherDeviceAccepted = "m.accepted", 22 | // SAS specific 23 | MismatchedCommitment = "m.mismatched_commitment", 24 | MismatchedSAS = "m.mismatched_sas", 25 | } 26 | -------------------------------------------------------------------------------- /src/matrix/room/members/MemberList.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ObservableMap} from "../../../observable"; 10 | import {RetainedValue} from "../../../utils/RetainedValue"; 11 | 12 | export class MemberList extends RetainedValue { 13 | constructor({members, closeCallback}) { 14 | super(closeCallback); 15 | this._members = new ObservableMap(); 16 | for (const member of members) { 17 | this._members.add(member.userId, member); 18 | } 19 | } 20 | 21 | afterSync(memberChanges) { 22 | for (const [userId, memberChange] of memberChanges.entries()) { 23 | this._members.set(userId, memberChange.member); 24 | } 25 | } 26 | 27 | get members() { 28 | return this._members; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/observable/map/index.ts: -------------------------------------------------------------------------------- 1 | // In order to avoid a circular dependency problem at runtime between BaseObservableMap 2 | // and the classes that extend it, it's important that: 3 | // 4 | // 1) It always remain the first module exported below. 5 | // 2) Anything that imports any of the classes in this module 6 | // ONLY import them from this index.ts file. 7 | // 8 | // See https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de 9 | // for more on why this discipline is necessary. 10 | export {BaseObservableMap} from './BaseObservableMap'; 11 | export type {Mapper, Updater, Comparator, Filter} from './BaseObservableMap'; 12 | export {ApplyMap} from './ApplyMap'; 13 | export {FilteredMap} from './FilteredMap'; 14 | export {JoinedMap} from './JoinedMap'; 15 | export {LogMap} from './LogMap'; 16 | export {MappedMap} from './MappedMap'; 17 | export {ObservableMap} from './ObservableMap'; 18 | export {ObservableValueMap} from './ObservableValueMap'; 19 | -------------------------------------------------------------------------------- /src/observable/value/index.ts: -------------------------------------------------------------------------------- 1 | // In order to avoid a circular dependency problem at runtime between BaseObservableValue 2 | // and the classes that extend it, it's important that: 3 | // 4 | // 1) It always remain the first module exported below. 5 | // 2) Anything that imports any of the classes in this module 6 | // ONLY import them from this index.ts file. 7 | // 8 | // See https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de 9 | // for more on why this discipline is necessary. 10 | export {BaseObservableValue} from './BaseObservableValue'; 11 | export {EventObservableValue} from './EventObservableValue'; 12 | export {FlatMapObservableValue} from './FlatMapObservableValue'; 13 | export {PickMapObservableValue} from './PickMapObservableValue'; 14 | export {RetainedObservableValue} from './RetainedObservableValue'; 15 | export {MapSizeObservableValue} from './MapSizeObservableValue'; 16 | export {ObservableValue} from './ObservableValue'; 17 | -------------------------------------------------------------------------------- /src/matrix/registration/stages/TermsAuth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {AuthenticationData} from "../types"; 10 | import {BaseRegistrationStage} from "./BaseRegistrationStage"; 11 | 12 | export class TermsAuth extends BaseRegistrationStage { 13 | generateAuthenticationData(): AuthenticationData { 14 | return { 15 | session: this._session, 16 | type: this.type, 17 | // No other auth data needed for m.login.terms 18 | }; 19 | } 20 | 21 | get type(): string { 22 | return "m.login.terms"; 23 | } 24 | 25 | get privacyPolicy() { 26 | return this._params?.policies["privacy_policy"]; 27 | } 28 | 29 | get termsOfService() { 30 | return this._params?.policies["terms_of_service"]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /doc/architecture/sync-updates.md: -------------------------------------------------------------------------------- 1 | # persistance vs model update of a room 2 | 3 | ## persist first, return update object, update model with update object 4 | - we went with this 5 | ## update model first, return update object, persist with update object 6 | - not all models exist at all times (timeline only when room is "open"), 7 | so model to create timeline update object might not exist for persistence need 8 | 9 | ## persist, update, each only based on sync data (independent of each other) 10 | - possible inconsistency between syncing and loading from storage as they are different code paths 11 | + storage code remains very simple and focussed 12 | 13 | ## updating model and persisting in one go 14 | - if updating model needs to do anything async, it needs to postpone it or the txn will be closed 15 | 16 | ## persist first, read from storage to update model 17 | + guaranteed consistency between what is on screen and in storage 18 | - slower as we need to reread what was just synced every time (big accounts with frequent updates) 19 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/AccountDataStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {Store} from "../Store"; 9 | import {Content} from "../../types"; 10 | 11 | export interface AccountDataEntry { 12 | type: string; 13 | content: Content; 14 | } 15 | 16 | export class AccountDataStore { 17 | private _store: Store; 18 | 19 | constructor(store: Store) { 20 | this._store = store; 21 | } 22 | 23 | async get(type: string): Promise { 24 | return await this._store.get(type); 25 | } 26 | 27 | set(event: AccountDataEntry): void { 28 | this._store.put(event); 29 | } 30 | 31 | async getAll(): Promise> { 32 | return await this._store.selectAll(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/mac.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import type {ILogItem} from "../../../logging/types"; 9 | import type {MacMethod} from "./stages/constants"; 10 | 11 | const macMethods: Record = { 12 | "hkdf-hmac-sha256": "calculate_mac", 13 | "org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64", 14 | "hkdf-hmac-sha256.v2": "calculate_mac_fixed_base64", 15 | "hmac-sha256": "calculate_mac_long_kdf", 16 | }; 17 | 18 | export function createCalculateMAC(olmSAS: Olm.SAS, method: MacMethod) { 19 | return function (input: string, info: string, log: ILogItem): string { 20 | return log.wrap({ l: "calculate MAC", method}, () => { 21 | const mac = olmSAS[macMethods[method]](input, info); 22 | return mac; 23 | }); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/stages/SendReadyStage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {BaseSASVerificationStage} from "./BaseSASVerificationStage"; 9 | import {VerificationEventType} from "../channel/types"; 10 | import {SelectVerificationMethodStage} from "./SelectVerificationMethodStage"; 11 | 12 | export class SendReadyStage extends BaseSASVerificationStage { 13 | async completeStage() { 14 | await this.log.wrap("SendReadyStage.completeStage", async (log) => { 15 | const content = { 16 | "from_device": this.ourUserDeviceId, 17 | "methods": ["m.sas.v1"], 18 | }; 19 | await this.channel.send(VerificationEventType.Ready, content, log); 20 | this.setNextStage(new SelectVerificationMethodStage(this.options)); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/ImageTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | import {BaseMediaTile} from "./BaseMediaTile.js"; 11 | 12 | export class ImageTile extends BaseMediaTile { 13 | constructor(entry, options) { 14 | super(entry, options); 15 | this._lightboxUrl = this.urlRouter.urlForSegments([ 16 | // ensure the right room is active if in grid view 17 | this.navigation.segment("room", this._room.id), 18 | this.navigation.segment("lightbox", this._entry.id) 19 | ]); 20 | } 21 | 22 | get lightboxUrl() { 23 | if (!this.isPending) { 24 | return this._lightboxUrl; 25 | } 26 | return ""; 27 | } 28 | 29 | get shape() { 30 | return "image"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/matrix/login/TokenLoginMethod.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {makeTxnId} from "../common.js"; 10 | import {ILogItem} from "../../logging/types"; 11 | import {ILoginMethod} from "./LoginMethod"; 12 | import {HomeServerApi} from "../net/HomeServerApi.js"; 13 | 14 | export class TokenLoginMethod implements ILoginMethod { 15 | private readonly _loginToken: string; 16 | public readonly homeserver: string; 17 | 18 | constructor({ homeserver, loginToken }: { homeserver: string, loginToken: string}) { 19 | this.homeserver = homeserver; 20 | this._loginToken = loginToken; 21 | } 22 | 23 | async login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise> { 24 | return await hsApi.tokenLogin(this._loginToken, makeTxnId(), deviceName, {log}).response(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/mergeMap.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function mergeMap(src: Map | undefined, dst: Map): void { 10 | if (src) { 11 | for (const [key, value] of src.entries()) { 12 | dst.set(key, value); 13 | } 14 | } 15 | } 16 | 17 | export function tests() { 18 | return { 19 | "mergeMap with src": assert => { 20 | const src = new Map(); 21 | src.set(1, "a"); 22 | const dst = new Map(); 23 | dst.set(2, "b"); 24 | mergeMap(src, dst); 25 | assert.equal(dst.get(1), "a"); 26 | assert.equal(dst.get(2), "b"); 27 | assert.equal(src.get(2), null); 28 | }, 29 | "mergeMap without src doesn't fail": () => { 30 | mergeMap(undefined, new Map()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/hangup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/voice-call.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/platform/web/assets/icon-maskable.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM} docker.io/node:alpine as builder 2 | RUN apk add --no-cache git python3 build-base 3 | 4 | WORKDIR /app 5 | 6 | # Copy package.json and yarn.lock and install dependencies first to speed up subsequent builds 7 | COPY package.json yarn.lock /app/ 8 | RUN yarn install 9 | 10 | COPY . /app 11 | RUN yarn build 12 | 13 | # Because we will be running as an unprivileged user, we need to make sure that the config file is writable 14 | # So, we will copy the default config to the /tmp folder that will be writable at runtime 15 | RUN mv -f target/config.json /config.json.bundled \ 16 | && ln -sf /tmp/config.json target/config.json 17 | 18 | FROM --platform=${TARGETPLATFORM} docker.io/nginxinc/nginx-unprivileged:alpine 19 | 20 | # Copy the dynamic config script 21 | COPY ./docker/dynamic-config.sh /docker-entrypoint.d/99-dynamic-config.sh 22 | # And the bundled config file 23 | COPY --from=builder /config.json.bundled /config.json.bundled 24 | 25 | # Copy the built app from the first build stage 26 | COPY --from=builder /app/target /usr/share/nginx/html 27 | -------------------------------------------------------------------------------- /scripts/sdk/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Exit whenever one of the commands fail with a non-zero exit code 3 | set -e 4 | set -o pipefail 5 | # Enable extended globs so we can use the `!(filename)` glob syntax 6 | shopt -s extglob 7 | 8 | # Only remove the directory contents instead of the whole directory to maintain 9 | # the `npm link`/`yarn link` symlink 10 | rm -rf target/* 11 | yarn run vite build -c vite.sdk-assets-config.js --mode sdk 12 | yarn run vite build -c vite.sdk-lib-config.js --mode sdk 13 | yarn tsc -p tsconfig-declaration.json 14 | ./scripts/sdk/create-manifest.js ./target/package.json 15 | mkdir target/paths 16 | # this doesn't work, the ?url imports need to be in the consuming project, so disable for now 17 | # ./scripts/sdk/transform-paths.js ./src/platform/web/sdk/paths/vite.js ./target/paths/vite.js 18 | cp doc/SDK.md target/README.md 19 | cp doc/SDK-CHANGELOG.md target/CHANGELOG.md 20 | pushd target/asset-build 21 | rm index.html 22 | popd 23 | pushd target/asset-build/assets 24 | # Remove all `*.wasm` and `*.js` files except for `main.js` 25 | rm !(main).js *.wasm 26 | popd 27 | -------------------------------------------------------------------------------- /src/domain/session/toast/BaseToastNotificationViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ErrorReportViewModel} from "../../ErrorReportViewModel"; 10 | import {Options as BaseOptions} from "../../ViewModel"; 11 | import type {Session} from "../../../matrix/Session.js"; 12 | import {SegmentType} from "../../navigation"; 13 | 14 | export type BaseClassOptions = { 15 | dismiss: () => void; 16 | session: Session; 17 | } & BaseOptions; 18 | 19 | export abstract class BaseToastNotificationViewModel = BaseClassOptions> extends ErrorReportViewModel { 20 | constructor(options: O) { 21 | super(options); 22 | } 23 | 24 | dismiss(): void { 25 | this.getOption("dismiss")(); 26 | } 27 | 28 | abstract get kind(): string; 29 | } 30 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/SASRequest.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {CrossSigning} from "../CrossSigning"; 10 | import type {Room} from "../../room/Room.js"; 11 | import type {ILogItem} from "../../../logging/types"; 12 | 13 | export class SASRequest { 14 | constructor(public readonly startingMessage: any) {} 15 | 16 | get deviceId(): string { 17 | return this.startingMessage.content.from_device; 18 | } 19 | 20 | get sender(): string { 21 | return this.startingMessage.sender; 22 | } 23 | 24 | get id(): string { 25 | return this.startingMessage.content.transaction_id ?? this.startingMessage.eventId; 26 | } 27 | 28 | async reject(crossSigning: CrossSigning, room: Room, log: ILogItem): Promise { 29 | const sas = crossSigning.startVerification(this, room, log); 30 | await sas?.abort(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/left-panel.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | .LeftPanel { 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .LeftPanel .utilities { 15 | display: flex; 16 | } 17 | 18 | .LeftPanel .utilities .FilterField { 19 | flex: 1; 20 | min-width: 0; 21 | } 22 | 23 | .LeftPanel ul { 24 | list-style: none; 25 | padding: 0; 26 | margin: 0; 27 | } 28 | 29 | .RoomList { 30 | flex: 1 0 0; 31 | overflow-y: auto; 32 | overscroll-behavior: contain; 33 | } 34 | 35 | .RoomList > li > a { 36 | display: flex; 37 | align-items: center; 38 | } 39 | 40 | .RoomList .description { 41 | margin: 0; 42 | flex: 1 1 0; 43 | min-width: 0; 44 | display: flex; 45 | } 46 | 47 | .RoomList .description > .name { 48 | overflow: hidden; 49 | white-space: nowrap; 50 | text-overflow: ellipsis; 51 | flex: 1; 52 | } 53 | -------------------------------------------------------------------------------- /src/matrix/e2ee/olm/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {OLM_ALGORITHM} from "../common"; 10 | 11 | export const enum OlmPayloadType { 12 | PreKey = 0, 13 | Normal = 1 14 | } 15 | 16 | export type OlmMessage = { 17 | type?: OlmPayloadType, 18 | body?: string 19 | } 20 | 21 | export type OlmEncryptedMessageContent = { 22 | algorithm?: typeof OLM_ALGORITHM 23 | sender_key?: string, 24 | ciphertext?: { 25 | [deviceCurve25519Key: string]: OlmMessage 26 | } 27 | } 28 | 29 | export type OlmEncryptedEvent = { 30 | type?: "m.room.encrypted", 31 | content?: OlmEncryptedMessageContent 32 | sender?: string 33 | } 34 | 35 | export type OlmPayload = { 36 | type?: string; 37 | content?: Record; 38 | sender?: string; 39 | recipient?: string; 40 | recipient_keys?: {ed25519?: string}; 41 | keys?: {ed25519?: string}; 42 | } 43 | -------------------------------------------------------------------------------- /src/matrix/login/PasswordLoginMethod.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ILogItem} from "../../logging/types"; 10 | import {ILoginMethod} from "./LoginMethod"; 11 | import {HomeServerApi} from "../net/HomeServerApi.js"; 12 | 13 | export class PasswordLoginMethod implements ILoginMethod { 14 | private readonly _username: string; 15 | private readonly _password: string; 16 | public readonly homeserver: string; 17 | 18 | constructor({username, password, homeserver}: {username: string, password: string, homeserver: string}) { 19 | this._username = username; 20 | this._password = password; 21 | this.homeserver = homeserver; 22 | } 23 | 24 | async login(hsApi: HomeServerApi, deviceName: string, log: ILogItem): Promise> { 25 | return await hsApi.passwordLogin(this._username, this._password, deviceName, {log}).response(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/enable-grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/SessionStatusView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../general/TemplateView"; 10 | import {spinner} from "../common.js"; 11 | 12 | export class SessionStatusView extends TemplateView { 13 | render(t, vm) { 14 | return t.div({className: { 15 | "SessionStatusView": true, 16 | "hidden": vm => !vm.isShown, 17 | }}, [ 18 | spinner(t, {hidden: vm => !vm.isWaiting}), 19 | t.p(vm => vm.statusLabel), 20 | t.if(vm => vm.isConnectNowShown, t => t.button({className: "link", onClick: () => vm.connectNow()}, "Retry now")), 21 | t.if(vm => vm.isSecretStorageShown, t => t.a({href: vm.setupKeyBackupUrl}, "Go to settings")), 22 | t.if(vm => vm.canDismiss, t => t.div({className: "end"}, t.button({className: "dismiss", onClick: () => vm.dismiss()}))), 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/recursivelyAssign.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2019 The Matrix.org Foundation C.I.C. 4 | Copyright 2015, 2016 OpenMarket Ltd 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | 11 | /** 12 | * This function is similar to Object.assign() but it assigns recursively and 13 | * allows you to ignore nullish values from the source 14 | * 15 | * @param {Object} target 16 | * @param {Object} source 17 | * @returns the target object 18 | */ 19 | export function recursivelyAssign(target: Object, source: Object, ignoreNullish = false): any { 20 | for (const [sourceKey, sourceValue] of Object.entries(source)) { 21 | if (target[sourceKey] instanceof Object && sourceValue) { 22 | recursivelyAssign(target[sourceKey], sourceValue); 23 | continue; 24 | } 25 | if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) { 26 | target[sourceKey] = sourceValue; 27 | continue; 28 | } 29 | } 30 | return target; 31 | } -------------------------------------------------------------------------------- /src/observable/value/EventObservableValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseObservableValue} from "./index"; 10 | import {EventEmitter} from "../../utils/EventEmitter"; 11 | 12 | export class EventObservableValue> extends BaseObservableValue { 13 | private eventSubscription: () => void; 14 | 15 | constructor( 16 | private readonly value: V, 17 | private readonly eventName: keyof T 18 | ) { 19 | super(); 20 | } 21 | 22 | onSubscribeFirst(): void { 23 | this.eventSubscription = this.value.disposableOn(this.eventName, () => { 24 | this.emit(this.value); 25 | }); 26 | super.onSubscribeFirst(); 27 | } 28 | 29 | onUnsubscribeLast(): void { 30 | this.eventSubscription!(); 31 | super.onUnsubscribeLast(); 32 | } 33 | 34 | get(): V { 35 | return this.value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/observable/list/common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | Copyright 2020 Bruno Windels 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | import {BaseObservableList} from "./BaseObservableList"; 10 | 11 | /* inline update of item in collection backed by array, without replacing the preexising item */ 12 | export function findAndUpdateInArray( 13 | predicate: (value: T) => boolean, 14 | array: T[], 15 | observable: BaseObservableList, 16 | updater: (value: T) => any | false 17 | ): boolean { 18 | const index = array.findIndex(predicate); 19 | if (index !== -1) { 20 | const value = array[index]; 21 | // allow bailing out of sending an emit if updater determined its not needed 22 | const params = updater(value); 23 | if (params !== false) { 24 | observable.emitUpdate(index, value, params); 25 | } 26 | // found 27 | return true; 28 | } 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /src/platform/web/dom/OnlineStatus.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseObservableValue} from "../../../observable/value"; 10 | 11 | export class OnlineStatus extends BaseObservableValue { 12 | constructor() { 13 | super(); 14 | this._onOffline = this._onOffline.bind(this); 15 | this._onOnline = this._onOnline.bind(this); 16 | } 17 | 18 | _onOffline() { 19 | this.emit(false); 20 | } 21 | 22 | _onOnline() { 23 | this.emit(true); 24 | } 25 | 26 | get() { 27 | return navigator.onLine; 28 | } 29 | 30 | onSubscribeFirst() { 31 | window.addEventListener('offline', this._onOffline); 32 | window.addEventListener('online', this._onOnline); 33 | } 34 | 35 | onUnsubscribeLast() { 36 | window.removeEventListener('offline', this._onOffline); 37 | window.removeEventListener('online', this._onOnline); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/stages/SendRequestVerificationStage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {BaseSASVerificationStage} from "./BaseSASVerificationStage"; 9 | import {SelectVerificationMethodStage} from "./SelectVerificationMethodStage"; 10 | import {VerificationEventType} from "../channel/types"; 11 | 12 | export class SendRequestVerificationStage extends BaseSASVerificationStage { 13 | async completeStage() { 14 | await this.log.wrap("SendRequestVerificationStage.completeStage", async (log) => { 15 | const content = { 16 | "from_device": this.ourUserDeviceId, 17 | "methods": ["m.sas.v1"], 18 | }; 19 | await this.channel.send(VerificationEventType.Request, content, log); 20 | this.setNextStage(new SelectVerificationMethodStage(this.options)); 21 | await this.channel.waitForEvent(VerificationEventType.Ready); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mocks/Request.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {AbortError} from "../utils/error"; 10 | 11 | export class BaseRequest { 12 | constructor() { 13 | this._responsePromise = new Promise((resolve, reject) => { 14 | this.resolve = resolve; 15 | this.reject = reject; 16 | }); 17 | this.responded = false; 18 | this.aborted = false; 19 | } 20 | 21 | _respond(value) { 22 | this.responded = true; 23 | this.resolve(value); 24 | return this; 25 | } 26 | 27 | abort() { 28 | this.aborted = true; 29 | this.reject(new AbortError()); 30 | } 31 | 32 | response() { 33 | return this._responsePromise; 34 | } 35 | } 36 | 37 | // this is a NetworkRequest as used by HomeServerApi 38 | export class Request extends BaseRequest { 39 | respond(status, body) { 40 | return this._respond({status, body}); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/sortedIndex.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | /** 10 | * @license 11 | * Based off baseSortedIndex function in Lodash 12 | * Copyright JS Foundation and other contributors 13 | * Released under MIT license 14 | * Based on Underscore.js 1.8.3 15 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 16 | */ 17 | export function sortedIndex(array: T[], value: T, comparator: (x:T, y:T) => number): number { 18 | let low = 0; 19 | let high = array.length; 20 | 21 | while (low < high) { 22 | let mid = (low + high) >>> 1; 23 | let cmpResult = comparator(value, array[mid]); 24 | 25 | if (cmpResult > 0) { 26 | low = mid + 1; 27 | } else if (cmpResult < 0) { 28 | high = mid; 29 | } else { 30 | low = high = mid; 31 | } 32 | } 33 | return high; 34 | } 35 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/VideoTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | 10 | import {BaseMediaTile} from "./BaseMediaTile.js"; 11 | 12 | export class VideoTile extends BaseMediaTile { 13 | async loadVideo() { 14 | const file = this._getContent().file; 15 | if (file && !this._decryptedFile) { 16 | this._decryptedFile = await this._loadEncryptedFile(file); 17 | this.emitChange("videoUrl"); 18 | } 19 | } 20 | 21 | get videoUrl() { 22 | if (this._decryptedFile) { 23 | return this._decryptedFile.url; 24 | } 25 | const mxcUrl = this._getContent()?.url; 26 | if (typeof mxcUrl === "string") { 27 | return this._mediaRepository.mxcUrl(mxcUrl); 28 | } 29 | return ""; 30 | } 31 | 32 | get shape() { 33 | return "video"; 34 | } 35 | 36 | _isMainResourceImage() { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/matrix/net/types/response.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export type Attachment = { 10 | body: string; 11 | info: AttachmentInfo; 12 | msgtype: string; 13 | url?: string; 14 | file?: EncryptedFile; 15 | filename?: string; 16 | } 17 | 18 | export type EncryptedFile = { 19 | key: JsonWebKey; 20 | iv: string; 21 | hashes: { 22 | sha256: string; 23 | }; 24 | url: string; 25 | v: string; 26 | mimetype?: string; 27 | } 28 | 29 | type AttachmentInfo = { 30 | h?: number; 31 | w?: number; 32 | mimetype: string; 33 | size: number; 34 | duration?: number; 35 | thumbnail_url?: string; 36 | thumbnail_file?: EncryptedFile; 37 | thumbnail_info?: ThumbnailInfo; 38 | } 39 | 40 | type ThumbnailInfo = { 41 | h: number; 42 | w: number; 43 | mimetype: string; 44 | size: number; 45 | } 46 | 47 | export type VersionResponse = { 48 | versions: string[]; 49 | unstable_features?: Record; 50 | } 51 | -------------------------------------------------------------------------------- /src/platform/web/ui/login/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function hydrogenGithubLink(t) { 10 | if (DEFINE_VERSION === "develop") { 11 | return t.a( 12 | { 13 | target: "_blank", 14 | href: `https://github.com/vector-im/hydrogen-web`, 15 | }, 16 | `Hydrogen develop, ${DEFINE_GLOBAL_HASH}` 17 | ); 18 | } else if (DEFINE_VERSION && DEFINE_GLOBAL_HASH) { 19 | return t.a( 20 | { 21 | target: "_blank", 22 | href: `https://github.com/vector-im/hydrogen-web/releases/tag/v${DEFINE_VERSION}`, 23 | }, 24 | `Hydrogen v${DEFINE_VERSION} (${DEFINE_GLOBAL_HASH}) on Github` 25 | ); 26 | } else { 27 | return t.a( 28 | { 29 | target: "_blank", 30 | href: "https://github.com/vector-im/hydrogen-web", 31 | }, 32 | "Hydrogen on Github" 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/matrix/room/timeline/entries/NonPersistedEventEntry.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {EventEntry} from "./EventEntry.js"; 10 | 11 | // EventEntry but without the two properties that are populated via SyncWriter 12 | // Useful if you want to create an EventEntry that is ephemeral 13 | 14 | export class NonPersistedEventEntry extends EventEntry { 15 | get fragmentId() { 16 | throw new Error("Cannot access fragmentId for non-persisted EventEntry"); 17 | } 18 | 19 | get entryIndex() { 20 | throw new Error("Cannot access entryIndex for non-persisted EventEntry"); 21 | } 22 | 23 | get isNonPersisted() { 24 | return true; 25 | } 26 | 27 | // overridden here because we reuse addLocalRelation() for updating this entry 28 | // we don't want the RedactedTile created using this entry to ever show "is being redacted" 29 | get isRedacting() { 30 | return false; 31 | } 32 | 33 | get isRedacted() { 34 | return super.isRedacting; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/matrix/registration/stages/TokenAuth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {AuthenticationData, RegistrationParams} from "../types"; 10 | import {BaseRegistrationStage} from "./BaseRegistrationStage"; 11 | 12 | export class TokenAuth extends BaseRegistrationStage { 13 | private _token?: string; 14 | private readonly _type: string; 15 | 16 | constructor(session: string, params: RegistrationParams | undefined, type: string) { 17 | super(session, params); 18 | this._type = type; 19 | } 20 | 21 | 22 | generateAuthenticationData(): AuthenticationData { 23 | if (!this._token) { 24 | throw new Error("No token provided for TokenAuth"); 25 | } 26 | return { 27 | session: this._session, 28 | type: this._type, 29 | token: this._token, 30 | }; 31 | } 32 | 33 | setToken(token: string) { 34 | this._token = token; 35 | } 36 | 37 | get type(): string { 38 | return this._type; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/mocks/event.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export function createEvent(type, id = null, sender = null) { 10 | return {type, event_id: id, sender}; 11 | } 12 | 13 | export function withContent(content, event) { 14 | return Object.assign({}, event, {content}); 15 | } 16 | 17 | export function withSender(sender, event) { 18 | return Object.assign({}, event, {sender}); 19 | } 20 | 21 | export function withTextBody(body, event) { 22 | return withContent({body, msgtype: "m.text"}, event); 23 | } 24 | 25 | export function withTxnId(txnId, event) { 26 | return Object.assign({}, event, {unsigned: {transaction_id: txnId}}); 27 | } 28 | 29 | export function withRedacts(redacts, reason, event) { 30 | return Object.assign({redacts, content: {reason}}, event); 31 | } 32 | 33 | export function withReply(replyToId, event) { 34 | return withContent({ 35 | "m.relates_to": { 36 | "m.in_reply_to": { 37 | "event_id": replyToId 38 | } 39 | } 40 | }, event); 41 | } 42 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/mic-unmuted.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/ReactionsView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {ListView} from "../../../general/ListView"; 10 | import {TemplateView} from "../../../general/TemplateView"; 11 | 12 | export class ReactionsView extends ListView { 13 | constructor(reactionsViewModel) { 14 | const options = { 15 | className: "Timeline_messageReactions", 16 | tagName: "div", 17 | list: reactionsViewModel.reactions, 18 | onItemClick: reactionView => reactionView.onClick(), 19 | } 20 | super(options, reactionVM => new ReactionView(reactionVM)); 21 | } 22 | } 23 | 24 | class ReactionView extends TemplateView { 25 | render(t, vm) { 26 | return t.button({ 27 | className: { 28 | active: vm => vm.isActive, 29 | pending: vm => vm.isPending 30 | }, 31 | }, [vm.key, " ", vm => `${vm.count}`]); 32 | } 33 | 34 | onClick() { 35 | this.value.toggle(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 15 | 21 | 22 | 24 | 26 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/InviteStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {Store} from "../Store"; 9 | import {MemberData} from "./RoomMemberStore"; 10 | 11 | // TODO: Move to Invite when that's TypeScript. 12 | export interface InviteData { 13 | roomId: string; 14 | isEncrypted: boolean; 15 | isDirectMessage: boolean; 16 | name?: string; 17 | avatarUrl?: string; 18 | avatarColorId: number; 19 | canonicalAlias?: string; 20 | timestamp: number; 21 | joinRule: string; 22 | inviter?: MemberData; 23 | } 24 | 25 | export class InviteStore { 26 | private _inviteStore: Store; 27 | 28 | constructor(inviteStore: Store) { 29 | this._inviteStore = inviteStore; 30 | } 31 | 32 | getAll(): Promise { 33 | return this._inviteStore.selectAll(); 34 | } 35 | 36 | set(invite: InviteData): void { 37 | this._inviteStore.put(invite); 38 | } 39 | 40 | remove(roomId: string): void { 41 | this._inviteStore.delete(roomId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/matrix/error.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class WrappedError extends Error { 10 | constructor(message, cause) { 11 | super(`${message}: ${cause.message}`); 12 | this.cause = cause; 13 | } 14 | 15 | get name() { 16 | return "WrappedError"; 17 | } 18 | } 19 | 20 | export class HomeServerError extends Error { 21 | constructor(method, url, body, status) { 22 | super(`${body ? body.error : status} on ${method} ${url}`); 23 | this.errcode = body ? body.errcode : null; 24 | this.retry_after_ms = body ? body.retry_after_ms : 0; 25 | this.statusCode = status; 26 | } 27 | 28 | get name() { 29 | return "HomeServerError"; 30 | } 31 | } 32 | 33 | export {AbortError} from "../utils/error"; 34 | 35 | export class ConnectionError extends Error { 36 | constructor(message, isTimeout) { 37 | super(message || "ConnectionError"); 38 | this.isTimeout = isTimeout; 39 | } 40 | 41 | get name() { 42 | return "ConnectionError"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/platform/web/legacy-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | // polyfills needed for IE11 10 | import Promise from "es6-promise/lib/es6-promise/promise.js"; 11 | import {checkNeedsSyncPromise} from "../../matrix/storage/idb/utils"; 12 | 13 | if (typeof window.Promise === "undefined") { 14 | window.Promise = Promise; 15 | // TODO: should be awaited before opening any session in the picker 16 | checkNeedsSyncPromise(); 17 | } 18 | import "core-js/stable"; 19 | import "regenerator-runtime/runtime"; 20 | import "mdn-polyfills/Element.prototype.closest"; 21 | // olm.init needs utf-16le, and this polyfill was 22 | // the only one I could find supporting it. 23 | // TODO: because the library sees a commonjs environment, 24 | // it will also include the file supporting *all* the encodings, 25 | // weighing a good extra 500kb :-( 26 | import "text-encoding"; 27 | 28 | // TODO: contribute this to mdn-polyfills 29 | if (!Element.prototype.remove) { 30 | Element.prototype.remove = function remove() { 31 | this.parentNode.removeChild(this); 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/platform/web/theming/shared/color.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import * as pkg from 'off-color'; 9 | const offColor = pkg.offColor ?? pkg.default.offColor; 10 | 11 | export function derive(value, operation, argument, isDark) { 12 | const argumentAsNumber = parseInt(argument); 13 | if (isDark) { 14 | // For dark themes, invert the operation 15 | if (operation === 'darker') { 16 | operation = "lighter"; 17 | } 18 | else if (operation === 'lighter') { 19 | operation = "darker"; 20 | } 21 | } 22 | switch (operation) { 23 | case "darker": { 24 | const newColorString = offColor(value).darken(argumentAsNumber / 100).hex(); 25 | return newColorString; 26 | } 27 | case "lighter": { 28 | const newColorString = offColor(value).lighten(argumentAsNumber / 100).hex(); 29 | return newColorString; 30 | } 31 | case "alpha": { 32 | return offColor(value).rgba(argumentAsNumber / 100); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/error.css: -------------------------------------------------------------------------------- 1 | .ErrorView_block { 2 | background: var(--error-color); 3 | color: var(--fixed-white); 4 | margin: 16px; 5 | } 6 | 7 | .ErrorView.ErrorView_inline { 8 | color: var(--error-color); 9 | margin: 4px 0; 10 | padding: 4px 0; 11 | } 12 | 13 | .ErrorView.ErrorView_inline > p { 14 | margin: 0; 15 | } 16 | 17 | .ErrorView { 18 | font-weight: bold; 19 | margin: 16px; 20 | border-radius: 8px; 21 | padding: 12px; 22 | display: flex; 23 | gap: 8px; 24 | } 25 | 26 | .ErrorView_message { 27 | flex-basis: 0; 28 | flex-grow: 1; 29 | margin: 0px; 30 | word-break: break-all; 31 | word-break: break-word; 32 | align-self: center; 33 | } 34 | 35 | .ErrorView_submit { 36 | align-self: end; 37 | } 38 | 39 | .ErrorView_close { 40 | align-self: start; 41 | width: 16px; 42 | height: 16px; 43 | border: none; 44 | background: none; 45 | background-repeat: no-repeat; 46 | background-size: contain; 47 | cursor: pointer; 48 | } 49 | 50 | .ErrorView_block .ErrorView_close { 51 | background-image: url('icons/clear.svg?primary=fixed-white'); 52 | } 53 | 54 | .ErrorView_inline .ErrorView_close { 55 | background-image: url('icons/clear.svg?primary=text-color'); 56 | } 57 | -------------------------------------------------------------------------------- /src/matrix/verification/SAS/stages/SendKeyStage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import {BaseSASVerificationStage} from "./BaseSASVerificationStage"; 9 | import {VerificationEventType} from "../channel/types"; 10 | import {CalculateSASStage} from "./CalculateSASStage"; 11 | 12 | export class SendKeyStage extends BaseSASVerificationStage { 13 | async completeStage() { 14 | await this.log.wrap("SendKeyStage.completeStage", async (log) => { 15 | const ourSasKey = this.olmSAS.get_pubkey(); 16 | await this.channel.send(VerificationEventType.Key, {key: ourSasKey}, log); 17 | /** 18 | * We may have already got the key in SendAcceptVerificationStage, 19 | * in which case waitForEvent will return a resolved promise with 20 | * that content. Otherwise, waitForEvent will actually wait for the 21 | * key message. 22 | */ 23 | await this.channel.waitForEvent(VerificationEventType.Key); 24 | this.setNextStage(new CalculateSASStage(this.options)); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/matrix/registration/stages/BaseRegistrationStage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {AuthenticationData, RegistrationParams} from "../types"; 10 | 11 | export abstract class BaseRegistrationStage { 12 | protected _session: string; 13 | protected _nextStage: BaseRegistrationStage; 14 | protected readonly _params?: Record 15 | 16 | constructor(session: string, params?: RegistrationParams) { 17 | this._session = session; 18 | this._params = params; 19 | } 20 | 21 | /** 22 | * eg: m.login.recaptcha or m.login.dummy 23 | */ 24 | abstract get type(): string; 25 | 26 | /** 27 | * This method should return auth part that must be provided to 28 | * /register endpoint to successfully complete this stage 29 | */ 30 | /** @internal */ 31 | abstract generateAuthenticationData(): AuthenticationData; 32 | 33 | setNextStage(stage: BaseRegistrationStage) { 34 | this._nextStage = stage; 35 | } 36 | 37 | get nextStage(): BaseRegistrationStage { 38 | return this._nextStage; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/UpdateAction.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export class UpdateAction { 10 | constructor(remove, update, replace, updateParams) { 11 | this._remove = remove; 12 | this._update = update; 13 | this._replace = replace; 14 | this._updateParams = updateParams; 15 | } 16 | 17 | get shouldReplace() { 18 | return this._replace; 19 | } 20 | 21 | get shouldRemove() { 22 | return this._remove; 23 | } 24 | 25 | get shouldUpdate() { 26 | return this._update; 27 | } 28 | 29 | get updateParams() { 30 | return this._updateParams; 31 | } 32 | 33 | static Remove() { 34 | return new UpdateAction(true, false, false, null); 35 | } 36 | 37 | static Update(newParams) { 38 | return new UpdateAction(false, true, false, newParams); 39 | } 40 | 41 | static Nothing() { 42 | return new UpdateAction(false, false, false, null); 43 | } 44 | 45 | static Replace(params) { 46 | return new UpdateAction(false, false, true, params); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/domain/ErrorViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import { ViewModel, Options as BaseOptions } from "./ViewModel"; 10 | import {submitLogsFromSessionToDefaultServer} from "./rageshake"; 11 | import type { Session } from "../matrix/Session"; 12 | import type {SegmentType} from "./navigation/index"; 13 | 14 | type Options = { 15 | error: Error 16 | session: Session, 17 | onClose: () => void 18 | } & BaseOptions; 19 | 20 | export class ErrorViewModel = Options> extends ViewModel { 21 | get message(): string { 22 | return this.error.message; 23 | } 24 | 25 | get error(): Error { 26 | return this.getOption("error"); 27 | } 28 | 29 | close() { 30 | this.getOption("onClose")(); 31 | } 32 | 33 | async submitLogs(): Promise { 34 | try { 35 | await submitLogsFromSessionToDefaultServer(this.getOption("session"), this.platform); 36 | return true; 37 | } catch (err) { 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/rightpanel/MemberListView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {LazyListView} from "../../general/LazyListView"; 10 | import {MemberTileView} from "./MemberTileView.js"; 11 | import {TemplateView} from "../../general/TemplateView"; 12 | 13 | export class MemberListView extends TemplateView { 14 | render(t, vm) { 15 | const list = new LazyListView({ 16 | list: vm.memberTileViewModels, 17 | className: "MemberListView__list", 18 | itemHeight: 40 19 | }, tileViewModel => new MemberTileView(tileViewModel)); 20 | return t.div({ className: "MemberListView" }, [ 21 | t.div({ className: "MemberListView__invite-container" }, [ 22 | t.button( 23 | { 24 | className: "MemberListView__invite-btn button-action primary", 25 | onClick: () => vm.openInvitePanel(), 26 | }, 27 | vm.i18n`Invite to this room` 28 | ), 29 | ]), 30 | t.view(list), 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/sdk/transform-paths.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | This script transforms the string literals in the sdk path files to adjust paths 5 | from what they are at development time to what they will be in the sdk package. 6 | 7 | It does this by looking in all string literals in the paths file and looking for file names 8 | that we expect and need replacing (as they are bundled with the sdk). 9 | 10 | Usage: ./transform-paths.js 11 | */ 12 | 13 | const acorn = require("acorn"); 14 | const walk = require("acorn-walk") 15 | const escodegen = require("escodegen"); 16 | const fs = require("fs"); 17 | 18 | const code = fs.readFileSync(process.argv[2], {encoding: "utf8"}); 19 | const ast = acorn.parse(code, {ecmaVersion: "13", sourceType: "module"}); 20 | 21 | function changePrefix(value, file, newPrefix = "") { 22 | const idx = value.indexOf(file); 23 | if (idx !== -1) { 24 | return newPrefix + value.substr(idx); 25 | } 26 | return value; 27 | } 28 | 29 | walk.simple(ast, { 30 | Literal(node) { 31 | node.value = changePrefix(node.value, "download-sandbox.html", "../"); 32 | node.value = changePrefix(node.value, "main.js", "../"); 33 | } 34 | }); 35 | const transformedCode = escodegen.generate(ast); 36 | fs.writeFileSync(process.argv[3], transformedCode, {encoding: "utf8"}) 37 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | Copyright 2020 The Matrix.org Foundation C.I.C. 5 | 6 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 7 | Please see LICENSE files in the repository root for full details. 8 | */ 9 | @import url('font.css'); 10 | @import url('layout.css'); 11 | @import url('popup.css'); 12 | @import url('login.css'); 13 | @import url('left-panel.css'); 14 | @import url('right-panel.css'); 15 | @import url('room.css'); 16 | @import url('timeline.css'); 17 | @import url('avatar.css'); 18 | @import url('spinner.css'); 19 | @import url('form.css'); 20 | @import url('status.css'); 21 | 22 | /* only if the body contains the whole app (e.g. we're not embedded in a page), make some changes */ 23 | body.hydrogen { 24 | /* make sure to disable rubber-banding and pull to refresh in a PWA if we'd end up having a scrollbar */ 25 | overscroll-behavior: none; 26 | /* disable rubberband scrolling on document in IE11 */ 27 | overflow: hidden; 28 | } 29 | 30 | .hydrogen { 31 | margin: 0; 32 | } 33 | 34 | .hiddenWithLayout { 35 | visibility: hidden; 36 | } 37 | 38 | .hidden { 39 | display: none !important; 40 | } 41 | 42 | /* hide clear buttons in IE */ 43 | input::-ms-clear { 44 | display: none; 45 | } 46 | -------------------------------------------------------------------------------- /src/platform/web/ui/session/room/timeline/GapView.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {TemplateView} from "../../../general/TemplateView"; 10 | import {spinner} from "../../../common.js"; 11 | import {ErrorView} from "../../../general/ErrorView"; 12 | 13 | export class GapView extends TemplateView { 14 | // ignore other argument 15 | constructor(vm) { 16 | super(vm); 17 | } 18 | 19 | render(t, vm) { 20 | const className = { 21 | GapView: true, 22 | isLoading: vm => vm.isLoading, 23 | isAtTop: vm => vm.isAtTop, 24 | }; 25 | return t.li({ className }, [ 26 | t.div({class: "GapView_container"}, [ 27 | t.if(vm => vm.showSpinner, (t) => spinner(t)), 28 | t.span(vm => vm.status), 29 | ]), 30 | t.if(vm => !!vm.errorViewModel, t => { 31 | return t.view(new ErrorView(vm.errorViewModel, {inline: true})); 32 | }) 33 | ]); 34 | } 35 | 36 | /* This is called by the parent ListView, which just has 1 listener for the whole list */ 37 | onClick() {} 38 | } 39 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/element-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/domain/login/StartSSOLoginViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {SSOLoginHelper} from "../../matrix/login"; 10 | import {Options as BaseOptions, ViewModel} from "../ViewModel"; 11 | import type {LoginOptions} from "./LoginViewModel"; 12 | 13 | 14 | type Options = { 15 | loginOptions: LoginOptions | undefined; 16 | } & BaseOptions; 17 | 18 | export class StartSSOLoginViewModel extends ViewModel{ 19 | private _sso?: SSOLoginHelper; 20 | private _isBusy = false; 21 | 22 | constructor(options: Options) { 23 | super(options); 24 | this._sso = options.loginOptions!.sso; 25 | this._isBusy = false; 26 | } 27 | 28 | get isBusy(): boolean { return this._isBusy; } 29 | 30 | setBusy(status: boolean): void { 31 | this._isBusy = status; 32 | this.emitChange("isBusy"); 33 | } 34 | 35 | async startSSOLogin(): Promise { 36 | await this.platform.settingsStorage.setString("sso_ongoing_login_homeserver", this._sso!.homeserver); 37 | const link = this._sso!.createSSORedirectURL(this.urlRouter.createSSOCallbackURL()); 38 | this.platform.openUrl(link); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Container Image 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: [ 'v*' ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | env: 11 | IMAGE_NAME: ${{ github.repository }} 12 | REGISTRY: ghcr.io 13 | 14 | jobs: 15 | push: 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: read 20 | packages: write 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v3 25 | 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v2 28 | 29 | - name: Log into registry ${{ env.REGISTRY }} 30 | uses: docker/login-action@v2 31 | with: 32 | registry: ${{ env.REGISTRY }} 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Extract Docker metadata 37 | id: meta 38 | uses: docker/metadata-action@v4 39 | with: 40 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 41 | 42 | - name: Build and push Docker image 43 | uses: docker/build-push-action@v3 44 | with: 45 | push: ${{ github.event_name != 'pull_request' }} 46 | tags: ${{ steps.meta.outputs.tags }} 47 | labels: ${{ steps.meta.outputs.labels }} 48 | platforms: linux/amd64,linux/arm64,linux/arm/v7 49 | -------------------------------------------------------------------------------- /src/mocks/HomeServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseRequest} from "./Request.js"; 10 | 11 | // a request as returned by the HomeServerApi 12 | class HomeServerRequest extends BaseRequest { 13 | constructor(args) { 14 | super(); 15 | this.arguments = args; 16 | } 17 | 18 | respond(body) { 19 | return this._respond(body); 20 | } 21 | } 22 | 23 | class Target { 24 | constructor() { 25 | this.requests = {}; 26 | } 27 | } 28 | 29 | function handleMethod(target, name, ...args) { 30 | let requests = target.requests[name] 31 | if (!requests) { 32 | target.requests[name] = requests = []; 33 | } 34 | const request = new HomeServerRequest(args); 35 | requests.push(request); 36 | return request; 37 | } 38 | 39 | class Handler { 40 | get(target, prop) { 41 | return handleMethod.bind(null, target, prop); 42 | } 43 | } 44 | 45 | export class HomeServer { 46 | constructor() { 47 | this._target = new Target(); 48 | this.api = new Proxy(this._target, new Handler()); 49 | } 50 | 51 | get requests() { 52 | return this._target.requests; 53 | } 54 | } -------------------------------------------------------------------------------- /src/utils/crypto/hkdf.ts: -------------------------------------------------------------------------------- 1 | /* Copyright 2025 New Vector Ltd. 2 | * Copyright 2020 The Matrix.org Foundation C.I.C. 3 | * Copyright 2018 Jun Kurihara 4 | * 5 | * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | * Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {Crypto} from "../../platform/web/dom/Crypto.js"; 10 | 11 | // forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible 12 | export async function hkdf(cryptoDriver: Crypto, key: Uint8Array, salt: Uint8Array, info: Uint8Array, hash: "SHA-256" | "SHA-512", length: number): Promise { 13 | length = length / 8; 14 | const len = cryptoDriver.digestSize(hash); 15 | 16 | // RFC5869 Step 1 (Extract) 17 | const prk = await cryptoDriver.hmac.compute(salt, key, hash); 18 | 19 | // RFC5869 Step 2 (Expand) 20 | let t = new Uint8Array([]); 21 | const okm = new Uint8Array(Math.ceil(length / len) * len); 22 | for(let i = 0; i < Math.ceil(length / len); i++){ 23 | const concat = new Uint8Array(t.length + info.length + 1); 24 | concat.set(t); 25 | concat.set(info, t.length); 26 | concat.set(new Uint8Array([i+1]), t.length + info.length); 27 | t = await cryptoDriver.hmac.compute(prk, concat, hash); 28 | okm.set(t, len * i); 29 | } 30 | return okm.slice(0, length); 31 | } 32 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/quirks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | 10 | import {openDatabase, txnAsPromise, reqAsPromise} from "./utils"; 11 | 12 | // filed as https://bugs.webkit.org/show_bug.cgi?id=222746 13 | export async function detectWebkitEarlyCloseTxnBug(idbFactory: IDBFactory): Promise { 14 | const dbName = "hydrogen_webkit_test_inactive_txn_bug"; 15 | try { 16 | const db = await openDatabase(dbName, db => { 17 | db.createObjectStore("test", {keyPath: "key"}); 18 | }, 1, idbFactory); 19 | const readTxn = db.transaction(["test"], "readonly"); 20 | await reqAsPromise(readTxn.objectStore("test").get("somekey")); 21 | // schedule a macro task in between the two txns 22 | await new Promise(r => setTimeout(r, 0)); 23 | const writeTxn = db.transaction(["test"], "readwrite"); 24 | await Promise.resolve(); 25 | writeTxn.objectStore("test").add({key: "somekey", value: "foo"}); 26 | await txnAsPromise(writeTxn); 27 | db.close(); 28 | } catch (err) { 29 | if (err.name === "TransactionInactiveError") { 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | -------------------------------------------------------------------------------- /doc/implementation planning/PUSH.md: -------------------------------------------------------------------------------- 1 | # Push Notifications 2 | - we setup the app on the sygnal server, with an app_id (io.element.hydrogen.web), generating a key pair 3 | - we create a web push subscription, passing the server pub key, and get `endpoint`, `p256dh` and `auth` back. We put `webpush_endpoint` and `auth` in the push data, and use `p256dh` as the push key? 4 | - we call `POST /_matrix/client/r0/pushers/set` on the homeserver with the sygnal instance url. We pass the web push subscription as pusher data. 5 | - the homeserver wants to send out a notification, calling sygnal on `POST /_matrix/push/v1/notify` with for each device the pusher data. 6 | - we encrypt and send with the data in the data for each device in the notification 7 | - this wakes up the service worker 8 | - now we need to find which local session id this notification is for 9 | 10 | ## Testing/development 11 | 12 | - set up local synapse 13 | - set up local sygnal 14 | - write pushkin 15 | - configure "hydrogen" app in sygnal config with a webpush pushkin 16 | - start writing service worker code in hydrogen (we'll need to enable it for local dev) 17 | - try to get a notification through 18 | 19 | ## Questions 20 | 21 | - do we use the `event_id_only` format? 22 | - for e2ee rooms, are we fine with just showing "Bob sent you a message (in room if not DM)", or do we want to sync and show the actual message? perhaps former can be MVP. 23 | -------------------------------------------------------------------------------- /src/domain/session/verification/stages/DismissibleVerificationViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {SegmentType} from "../../../navigation/index"; 10 | import {ErrorReportViewModel} from "../../../ErrorReportViewModel"; 11 | import type {Options as BaseOptions} from "../../../ViewModel"; 12 | import type {Session} from "../../../../matrix/Session.js"; 13 | import type {SASVerification} from "../../../../matrix/verification/SAS/SASVerification"; 14 | 15 | type Options = BaseOptions & { 16 | sas: SASVerification; 17 | session: Session; 18 | }; 19 | 20 | export abstract class DismissibleVerificationViewModel extends ErrorReportViewModel { 21 | dismiss(): void { 22 | /** 23 | * If we're cross-signing another user, redirect to the room (which will just close the right panel). 24 | * If we're verifying a device, redirect to settings. 25 | */ 26 | if (this.getOption("sas").isCrossSigningAnotherUser) { 27 | const path = this.navigation.path.until("room"); 28 | this.navigation.applyPath(path); 29 | } else { 30 | this.navigation.push("settings", true); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/logging/LogFilter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {ILogItem, ISerializedItem} from "./types"; 10 | 11 | export enum LogLevel { 12 | All = 1, 13 | Debug, 14 | Detail, 15 | Info, 16 | Warn, 17 | Error, 18 | Fatal, 19 | Off 20 | } 21 | 22 | export class LogFilter { 23 | private _min?: LogLevel; 24 | private _parentFilter?: LogFilter; 25 | 26 | constructor(parentFilter?: LogFilter) { 27 | this._parentFilter = parentFilter; 28 | } 29 | 30 | filter(item: ILogItem, children: ISerializedItem[] | null): boolean { 31 | if (this._parentFilter) { 32 | if (!this._parentFilter.filter(item, children)) { 33 | return false; 34 | } 35 | } 36 | // neither our children or us have a loglevel high enough, filter out. 37 | if (this._min !== undefined && !Array.isArray(children) && item.logLevel < this._min) { 38 | return false; 39 | } else { 40 | return true; 41 | } 42 | } 43 | 44 | /* methods to build the filter */ 45 | minLevel(logLevel: LogLevel): LogFilter { 46 | this._min = logLevel; 47 | return this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/export.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import { iterateCursor, NOT_DONE, txnAsPromise } from "./utils"; 10 | import { STORE_NAMES, StoreNames } from "../common"; 11 | 12 | export type Export = { [storeName in StoreNames] : any[] } 13 | 14 | export async function exportSession(db: IDBDatabase): Promise { 15 | const txn = db.transaction(STORE_NAMES, "readonly"); 16 | const data = {}; 17 | await Promise.all(STORE_NAMES.map(async name => { 18 | const results: any[] = data[name] = []; // initialize in deterministic order 19 | const store = txn.objectStore(name); 20 | await iterateCursor(store.openCursor(), (value) => { 21 | results.push(value); 22 | return NOT_DONE; 23 | }); 24 | })); 25 | return data as Export; 26 | } 27 | 28 | export async function importSession(db: IDBDatabase, data: Export): Promise { 29 | const txn = db.transaction(STORE_NAMES, "readwrite"); 30 | for (const name of STORE_NAMES) { 31 | const store = txn.objectStore(name); 32 | for (const value of data[name]) { 33 | store.add(value); 34 | } 35 | } 36 | await txnAsPromise(txn); 37 | } 38 | -------------------------------------------------------------------------------- /src/matrix/room/state/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import type {Room} from "../Room"; 10 | import type {StateEvent} from "../../storage/types"; 11 | import type {Transaction} from "../../storage/idb/Transaction"; 12 | import type {ILogItem} from "../../../logging/types"; 13 | import type {MemberChange} from "../members/RoomMember"; 14 | import type {MemberSync} from "../timeline/persistence/MemberWriter"; 15 | 16 | /** used for Session.observeRoomState, which observes in all room, but without loading from storage 17 | * It receives the sync write transaction, so other stores can be updated as part of the same transaction. */ 18 | export interface RoomStateHandler { 19 | handleRoomState(room: Room, stateEvent: StateEvent, memberSync: MemberSync, syncWriteTxn: Transaction, log: ILogItem): Promise; 20 | updateRoomMembers(room: Room, memberChanges: Map): void; 21 | } 22 | 23 | /** 24 | * used for Room.observeStateType and Room.observeStateTypeAndKey 25 | * @internal 26 | * */ 27 | export interface StateObserver { 28 | handleStateEvent(event: StateEvent); 29 | load(roomId: string, txn: Transaction): Promise; 30 | setRemoveCallback(callback: () => void); 31 | } 32 | -------------------------------------------------------------------------------- /doc/implementation planning/REPLIES.md: -------------------------------------------------------------------------------- 1 | If we were to render replies in a smart way (instead of relying on the fallback), we would 2 | need to manually find entries that are pointed to be `in_reply_to`. Consulting the timeline 3 | code, it seems appropriate to add a `_replyingTo` field to a `BaseEventEntry` (much like we 4 | have `_pendingAnnotations` and `pendingRedactions`). We can then: 5 | * use `TilesCollection`'s `_findTileIdx` to find the tile of the message being replied to, 6 | and put a reference to its tile into the new tile being created (?). 7 | * It doesn't seem appropriate to add an additional argument to TileCreator, but we may 8 | want to re-use tiles instead of creating duplicate ones. Otherwise, of course, `tileCreator` 9 | can create more than one tile from an entry's `_replyingTo` field. 10 | * Resolve `_replyingTo` much like we resolve `redactingEntry` in timeline: search by `relatedTxnId` 11 | and `relatedEventId` if our entry is a reply (we can add an `isReply` flag there). 12 | * This works fine for local entries, which are loaded via an `AsyncMappedList`, but what 13 | about remote entries? They are not loaded asynchronously, and the fact that they are 14 | not a derived collection is used throughout `Timeline`. 15 | * Entries that don't have replies that are loadeded (but that are replies) probably need 16 | to be tracked somehow? 17 | * Then, on timeline add, check new IDs and update corresponding entries 18 | -------------------------------------------------------------------------------- /src/platform/web/ui/css/themes/element/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "name": "Element", 4 | "id": "element", 5 | "values": { 6 | "variants": { 7 | "light": { 8 | "base": true, 9 | "default": true, 10 | "name": "Light", 11 | "variables": { 12 | "background-color-primary": "#fff", 13 | "background-color-secondary": "#f6f6f6", 14 | "text-color": "#2E2F32", 15 | "accent-color": "#03b381", 16 | "error-color": "#FF4B55", 17 | "fixed-white": "#fff", 18 | "room-badge": "#61708b", 19 | "link-color": "#238cf5" 20 | } 21 | }, 22 | "dark": { 23 | "dark": true, 24 | "default": true, 25 | "name": "Dark", 26 | "variables": { 27 | "background-color-primary": "#21262b", 28 | "background-color-secondary": "#2D3239", 29 | "text-color": "#fff", 30 | "accent-color": "#03B381", 31 | "error-color": "#FF4B55", 32 | "fixed-white": "#fff", 33 | "room-badge": "#61708b", 34 | "link-color": "#238cf5" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/utils/timeFormatting.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export enum TimeScope { 10 | Minute = 60 * 1000, 11 | Hours = 60 * TimeScope.Minute, 12 | Day = 24 * TimeScope.Hours, 13 | } 14 | 15 | export function formatDuration(milliseconds: number): string { 16 | let days = 0; 17 | let hours = 0; 18 | let minutes = 0; 19 | if (milliseconds >= TimeScope.Day) { 20 | days = Math.floor(milliseconds / TimeScope.Day); 21 | milliseconds -= days * TimeScope.Day; 22 | } 23 | if (milliseconds >= TimeScope.Hours) { 24 | hours = Math.floor(milliseconds / TimeScope.Hours); 25 | milliseconds -= hours * TimeScope.Hours; 26 | } 27 | if (milliseconds >= TimeScope.Minute) { 28 | minutes = Math.floor(milliseconds / TimeScope.Minute); 29 | milliseconds -= minutes * TimeScope.Minute; 30 | } 31 | const seconds = Math.floor(milliseconds / 1000); 32 | let result = ""; 33 | if (days) { 34 | result = `${days}d `; 35 | } 36 | if (hours || days) { 37 | result += `${hours}h `; 38 | } 39 | if (minutes || hours || days) { 40 | result += `${minutes}m `; 41 | } 42 | result += `${seconds}s`; 43 | return result; 44 | } 45 | -------------------------------------------------------------------------------- /src/observable/map/ObservableValueMap.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseObservableMap} from "./BaseObservableMap"; 10 | import {BaseObservableValue} from "../value/BaseObservableValue"; 11 | import {SubscriptionHandle} from "../BaseObservable"; 12 | 13 | export class ObservableValueMap extends BaseObservableMap { 14 | private subscription?: SubscriptionHandle; 15 | 16 | constructor(private readonly key: K, private readonly observableValue: BaseObservableValue) { 17 | super(); 18 | } 19 | 20 | onSubscribeFirst() { 21 | this.subscription = this.observableValue.subscribe(value => { 22 | this.emitUpdate(this.key, value, undefined); 23 | }); 24 | super.onSubscribeFirst(); 25 | } 26 | 27 | onUnsubscribeLast() { 28 | this.subscription!(); 29 | super.onUnsubscribeLast(); 30 | } 31 | 32 | *[Symbol.iterator](): Iterator<[K, V]> { 33 | yield [this.key, this.observableValue.get()]; 34 | } 35 | 36 | get size(): number { 37 | return 1; 38 | } 39 | 40 | get(key: K): V | undefined { 41 | if (key == this.key) { 42 | return this.observableValue.get(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/linkify/regex.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | /* 10 | The regex is split into component strings; 11 | meaning that any escapes (\) must also 12 | be escaped. 13 | */ 14 | const scheme = "(?:https|http|ftp):\\/\\/"; 15 | const noSpaceNorPunctuation = "[^\\s.,?!)]"; 16 | const hostCharacter = "[a-zA-Z0-9:.\\[\\]-]"; 17 | 18 | /* 19 | Using non-consuming group here to combine two criteria for the last character. 20 | See point 1 below. 21 | */ 22 | const host = `${hostCharacter}*(?=${hostCharacter})${noSpaceNorPunctuation}`; 23 | 24 | /* 25 | Use sub groups so we accept just / or #; but if anything comes after it, 26 | it should not end with punctuation or space. 27 | */ 28 | const pathOrFragment = `(?:[\\/#](?:[^\\s]*${noSpaceNorPunctuation})?)`; 29 | 30 | /* 31 | Things to keep in mind: 32 | 1. URL must not contain non-ascii characters in host but may contain 33 | them in path or fragment components. 34 | https://matrix.org/ - valid 35 | https://matrix.org - invalid 36 | 2. Do not treat punctuation at the end as a part of the URL (.,?!) 37 | 3. Path/fragment is optional. 38 | */ 39 | const urlRegex = `${scheme}${host}${pathOrFragment}?`; 40 | 41 | export const regex = new RegExp(urlRegex, "gi"); 42 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/RedactedTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseMessageTile} from "./BaseMessageTile.js"; 10 | 11 | export class RedactedTile extends BaseMessageTile { 12 | get shape() { 13 | return "redacted"; 14 | } 15 | 16 | get description() { 17 | const {redactionReason} = this._entry; 18 | if (this.isRedacting) { 19 | if (redactionReason) { 20 | return this.i18n`This message is being deleted (${redactionReason})…`; 21 | } else { 22 | return this.i18n`This message is being deleted…`; 23 | } 24 | } else { 25 | if (redactionReason) { 26 | return this.i18n`This message has been deleted (${redactionReason}).`; 27 | } else { 28 | return this.i18n`This message has been deleted.`; 29 | } 30 | } 31 | } 32 | 33 | get isRedacting() { 34 | return this._entry.isRedacting; 35 | } 36 | 37 | /** override parent property to disable redacting, even if still pending */ 38 | get canRedact() { 39 | return false; 40 | } 41 | 42 | abortPendingRedaction() { 43 | return this._entry.abortPendingRedaction(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/domain/session/verification/stages/VerifyEmojisViewModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2023 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {SegmentType} from "../../../navigation/index"; 10 | import {ErrorReportViewModel} from "../../../ErrorReportViewModel"; 11 | import type {Options as BaseOptions} from "../../../ViewModel"; 12 | import type {Session} from "../../../../matrix/Session.js"; 13 | import type {CalculateSASStage} from "../../../../matrix/verification/SAS/stages/CalculateSASStage"; 14 | 15 | type Options = BaseOptions & { 16 | stage: CalculateSASStage; 17 | session: Session; 18 | }; 19 | 20 | export class VerifyEmojisViewModel extends ErrorReportViewModel { 21 | private _isWaiting: boolean = false; 22 | 23 | async setEmojiMatch(match: boolean) { 24 | await this.logAndCatch("VerifyEmojisViewModel.setEmojiMatch", async () => { 25 | await this.options.stage.setEmojiMatch(match); 26 | this._isWaiting = true; 27 | this.emitChange("isWaiting"); 28 | }); 29 | } 30 | 31 | get emojis() { 32 | return this.options.stage.emoji; 33 | } 34 | 35 | get kind(): string { 36 | return "verify-emojis"; 37 | } 38 | 39 | get isWaiting(): boolean { 40 | return this._isWaiting; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/platform/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hydrogen Chat 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/matrix/room/joinRoom.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2022 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | import type {Session} from "../Session.js"; 9 | import {RoomStatus} from "./common"; 10 | 11 | /** 12 | * Join a room and wait for it to arrive in the next sync 13 | * @param roomId The id of the room to join 14 | * @param session A session instance 15 | */ 16 | export async function joinRoom(roomId: string, session: Session): Promise { 17 | try { 18 | const internalRoomId = await session.joinRoom(roomId); 19 | const roomStatusObservable = await session.observeRoomStatus(internalRoomId); 20 | await roomStatusObservable.waitFor((status: RoomStatus) => status === RoomStatus.Joined); 21 | return internalRoomId; 22 | } 23 | catch (e) { 24 | if ((e.statusCode ?? e.status) === 400) { 25 | throw new Error(`'${roomId}' is not a legal room ID or alias`); 26 | } else if ((e.statusCode ?? e.status) === 404 || (e.statusCode ?? e.status) === 502 || e.message == "Internal Server eor") { 27 | throw new Error(`Room '${roomId}' could not be found`); 28 | } else if ((e.statusCode ?? e.status) === 403) { 29 | throw new Error(`You are not invited to join '${roomId}'`); 30 | } else { 31 | throw e; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/matrix/storage/idb/stores/RoomSummaryStore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 Bruno Windels 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | /** 10 | store contains: 11 | roomId 12 | name 13 | lastMessage 14 | unreadCount 15 | mentionCount 16 | isEncrypted 17 | isDirectMessage 18 | membership 19 | inviteCount 20 | joinCount 21 | */ 22 | import {Store} from "../Store"; 23 | import {SummaryData} from "../../../room/RoomSummary"; 24 | 25 | /** Used for both roomSummary and archivedRoomSummary stores */ 26 | export class RoomSummaryStore { 27 | private _summaryStore: Store; 28 | 29 | constructor(summaryStore: Store) { 30 | this._summaryStore = summaryStore; 31 | } 32 | 33 | getAll(): Promise { 34 | return this._summaryStore.selectAll(); 35 | } 36 | 37 | set(summary: SummaryData): void { 38 | this._summaryStore.put(summary); 39 | } 40 | 41 | get(roomId: string): Promise { 42 | return this._summaryStore.get(roomId); 43 | } 44 | 45 | async has(roomId: string): Promise { 46 | const fetchedKey = await this._summaryStore.getKey(roomId); 47 | return roomId === fetchedKey; 48 | } 49 | 50 | remove(roomId: string): void { 51 | this._summaryStore.delete(roomId); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/domain/session/room/timeline/tiles/EncryptedEventTile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2020 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | import {BaseTextTile} from "./BaseTextTile.js"; 10 | import {UpdateAction} from "../UpdateAction.js"; 11 | 12 | export class EncryptedEventTile extends BaseTextTile { 13 | updateEntry(entry, params) { 14 | const parentResult = super.updateEntry(entry, params); 15 | // event got decrypted, recreate the tile and replace this one with it 16 | if (entry.eventType !== "m.room.encrypted") { 17 | // the "shape" parameter trigger tile recreation in TimelineView 18 | return UpdateAction.Replace("shape"); 19 | } else { 20 | return parentResult; 21 | } 22 | } 23 | 24 | get shape() { 25 | return "message-status" 26 | } 27 | 28 | _getBody() { 29 | const decryptionError = this._entry.decryptionError; 30 | const code = decryptionError?.code; 31 | let string; 32 | if (code === "MEGOLM_NO_SESSION") { 33 | string = this.i18n`The sender hasn't sent us the key for this message yet.`; 34 | } else { 35 | string = decryptionError?.message || this.i18n`Could not decrypt message because of unknown reason.`; 36 | } 37 | return string; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/matrix/registration/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 New Vector Ltd. 3 | Copyright 2021 The Matrix.org Foundation C.I.C. 4 | 5 | SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial 6 | Please see LICENSE files in the repository root for full details. 7 | */ 8 | 9 | export type AccountDetails = { 10 | username: string | null; 11 | password: string; 12 | initialDeviceDisplayName: string; 13 | inhibitLogin: boolean; 14 | } 15 | 16 | export type RegistrationResponse = RegistrationResponseMoreDataNeeded | RegistrationResponseSuccess; 17 | 18 | export type RegistrationResponseMoreDataNeeded = { 19 | completed?: string[]; 20 | flows: RegistrationFlow[]; 21 | params: Record; 22 | session: string; 23 | status: 401; 24 | } 25 | 26 | export type RegistrationResponseSuccess = { 27 | user_id: string; 28 | device_id: string; 29 | access_token?: string; 30 | status: 200; 31 | } 32 | 33 | export type AuthData = { 34 | userId: string; 35 | deviceId: string; 36 | homeserver: string; 37 | accessToken?: string; 38 | } 39 | 40 | export type RegistrationFlow = { 41 | stages: string[]; 42 | } 43 | 44 | /* Types for Registration Stage */ 45 | export type AuthenticationData = { 46 | type: string; 47 | session: string; 48 | [key: string]: any; 49 | } 50 | 51 | // contains additional data needed to complete a stage, eg: link to privacy policy 52 | export type RegistrationParams = { 53 | [key: string]: any; 54 | } 55 | --------------------------------------------------------------------------------