├── .deepsource.toml ├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── dependabot.yml │ ├── main.yml │ ├── pull_request.yml │ ├── release.yml │ ├── stale.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── axolotl-web ├── .depcheckrc.yaml ├── .editorconfig ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── eslint.config.js ├── gettext.config.cjs ├── index.html ├── jsconfig.json ├── package.json ├── public │ ├── axolotl.ico │ ├── axolotl.png │ └── favicon.ico ├── src │ ├── App.vue │ ├── assets │ │ ├── dark.scss │ │ ├── fonts │ │ │ ├── ubuntu-r-webfont.woff │ │ │ └── ubuntu-r-webfont.woff2 │ │ ├── images │ │ │ ├── check-circle-outline.svg │ │ │ ├── check-circle-outline_dark.svg │ │ │ ├── double-check-filled.svg │ │ │ ├── double-check-filled_dark.svg │ │ │ ├── double-check.svg │ │ │ ├── double-check_dark.svg │ │ │ ├── loading.svg │ │ │ ├── play.svg │ │ │ ├── sending.svg │ │ │ ├── sending_dark.svg │ │ │ └── warning.svg │ │ ├── light.scss │ │ ├── logo.png │ │ └── style.scss │ ├── components │ │ ├── AddContactModal.vue │ │ ├── AddDeviceModal.vue │ │ ├── AddGroupMembersModal.vue │ │ ├── AttachmentBar.vue │ │ ├── ConfirmationModal.vue │ │ ├── EditContactModal.vue │ │ ├── ErrorModal.vue │ │ ├── IdentityModal.vue │ │ ├── ImportUnavailableModal.vue │ │ ├── ImportVcfModal.vue │ │ ├── LegacyHeader.vue │ │ ├── Message.vue │ │ └── VerificationPinInput.vue │ ├── config.js │ ├── helpers │ │ └── uuidCheck.js │ ├── layouts │ │ ├── Default.vue │ │ └── Legacy.vue │ ├── main.js │ ├── pages │ │ ├── About.vue │ │ ├── ChatList.vue │ │ ├── Contacts.vue │ │ ├── Debug.vue │ │ ├── DeviceLinking.vue │ │ ├── DeviceList.vue │ │ ├── EditContact.vue │ │ ├── EditGroup.vue │ │ ├── MessageList.vue │ │ ├── NewGroup.vue │ │ ├── OnBoarding.vue │ │ ├── Password.vue │ │ ├── Profile.vue │ │ ├── Register.vue │ │ ├── SetPassword.vue │ │ ├── SetUsername.vue │ │ ├── Settings.vue │ │ └── Verification.vue │ ├── router │ │ └── router.js │ └── store │ │ └── store.js ├── tests │ └── unit │ │ └── components │ │ └── Message.spec.js ├── translations │ └── translations.json ├── vite.config.js ├── vitest.config.js └── yarn.lock ├── build.rs ├── click ├── axolotl-helper ├── axolotl-push-helper.json ├── axolotl-push.apparmor ├── axolotl.apparmor ├── axolotl.content-hub ├── axolotl.desktop ├── axolotl.png ├── axolotl.url-dispatcher └── notification.sh ├── clickable.yaml ├── data ├── axolotl.desktop ├── axolotl.metainfo.xml └── icons │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── docs ├── CHANGELOG.md ├── DEBUG.md ├── INSTALL.md ├── NOTES.md ├── TRANSLATE.md └── architecture.png ├── examples └── dump │ ├── Cargo.toml │ └── dump_db.rs ├── flatpak ├── README.md ├── cargo-sources.json ├── node-sources.json └── org.nanuc.Axolotl.yml ├── guis └── qml │ └── ut │ └── MainUt.qml ├── manifest.json ├── po ├── LINGUAS ├── POTFILES ├── ar.po ├── be.po ├── bg.po ├── cs.po ├── da.po ├── de.po ├── el.po ├── es.po ├── fa.po ├── fi.po ├── fr.po ├── hr.po ├── hu.po ├── in.po ├── it.po ├── iw.po ├── ja.po ├── kn-rIN.po ├── ko.po ├── mk.po ├── nb.po ├── nl.po ├── no.po ├── pl.po ├── pt-BR.po ├── pt.po ├── ro.po ├── ru.po ├── sk.po ├── sl.po ├── sr.po ├── sv.po ├── ta.po ├── textsecure.nanuc.pot ├── tr.po ├── vi.po └── zh-rCN.po ├── screenshot.png ├── snap ├── gui │ ├── axolotl.desktop │ └── axolotl.png └── snapcraft.yaml ├── src ├── error.rs ├── handlers.rs ├── handlers │ └── registration.rs ├── lib.rs ├── main.rs ├── manager_thread.rs ├── messages.rs └── requests.rs ├── tauri.conf.json └── vetur.config.js /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "rust" 5 | 6 | [analyzers.meta] 7 | msrv = "stable" 8 | 9 | [[analyzers]] 10 | name = "javascript" 11 | 12 | [analyzers.meta] 13 | plugins = ["vue"] 14 | environment = [ 15 | "mocha", 16 | "vitest" 17 | ] 18 | 19 | [[transformers]] 20 | name = "prettier" 21 | 22 | [[transformers]] 23 | name = "rustfmt" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | max_line_length = 100 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | patreon: nanuc 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | [Description of the issue] 13 | 14 | ### Steps to Reproduce 15 | 16 | 1. [First Step] 17 | 2. [Second Step] 18 | 3. [and so on...] 19 | 20 | **Expected behavior:** [What you expect to happen] 21 | 22 | **Actual behavior:** [What actually happens] 23 | 24 | #### Versions 25 | 26 | Please provide the Version as written in `Settings->About Axolotl` 27 | 28 | #### Device 29 | What device or OS are you using? 30 | 31 | #### Link to Debug Log 32 | Please provide a link to debug from ~/.cache/upstart/application-click-axolotl.nanuc_axolotl*.log 33 | Be careful it contains sensible data -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/axolotl-web" 8 | schedule: 9 | interval: "daily" 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | - package-ecosystem: "cargo" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot post-run 2 | 3 | on: pull_request 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | flatpak-sources-update: 11 | name: Update Flatpak sources 12 | # Avoid infinit loops of this workflow pushing and triggering another workflow 13 | # We do want to run the other workflows to build and test but do not want to run this one again. 14 | # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#prevent-infinite-loop-when-using-a-personal-access-token 15 | if: github.actor == 'dependabot[bot]' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | # Using an SSH deploy key to allow running workflows triggered by push event 21 | # https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#commits-made-by-this-action-do-not-trigger-new-workflow-runs 22 | # NOTE for actions triggered by dependabot, only the dependabot secrets are available 23 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#responding-to-events 24 | ssh-key: "${{ secrets.DEPENDABOT_SSH_KEY }}" 25 | # Fetch whole history as we want to commit and push 26 | fetch-depth: 0 27 | 28 | - name: Download flatpak-builder-tools 29 | run: | 30 | curl -L https://github.com/flatpak/flatpak-builder-tools/archive/refs/heads/master.tar.gz | tar xz 31 | mv flatpak-builder-tools-master flatpak-builder-tools 32 | 33 | - name: Install flatpak-builder-tools dependencies 34 | run: | 35 | sudo apt install -y pipx python3 python3-aiohttp python3-toml python3-yaml 36 | 37 | - name: Update NodeJS sources 38 | working-directory: flatpak-builder-tools/node 39 | run: | 40 | pipx install . 41 | flatpak-node-generator yarn ${{ github.workspace }}/axolotl-web/yarn.lock -o ${{ github.workspace }}/flatpak/node-sources.json 42 | 43 | - name: Update Cargo sources 44 | working-directory: flatpak-builder-tools/cargo 45 | run: python3 flatpak-cargo-generator.py ${{ github.workspace }}/Cargo.lock -o ${{ github.workspace }}/flatpak/cargo-sources.json 46 | 47 | - uses: stefanzweifel/git-auto-commit-action@v5 48 | with: 49 | # Ignore other changes like the cloned flatpak-builder-tools repo 50 | file_pattern: "flatpak/*-sources.json" 51 | # Do not block dependabot from updating this PR: https://github.com/dependabot/dependabot-core/issues/1758 52 | commit_message: "[dependabot skip] Update Flatpak sources" 53 | commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" 54 | # Do not overwrite in case our version is outdated 55 | push_options: "--force-with-lease" 56 | 57 | auto-approve: 58 | if: github.actor == 'dependabot[bot]' 59 | runs-on: ubuntu-latest 60 | permissions: 61 | pull-requests: write 62 | steps: 63 | - name: Auto approve Dependabot PRs 64 | run: gh pr review --approve "$PR_URL" 65 | env: 66 | PR_URL: ${{github.event.pull_request.html_url}} 67 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 68 | 69 | auto-merge: 70 | if: github.actor == 'dependabot[bot]' 71 | runs-on: ubuntu-latest 72 | permissions: 73 | contents: write 74 | pull-requests: write 75 | steps: 76 | # NOTE Auto-merge is blocked until branch protection rules are fulfilled 77 | - name: Enable auto-merge for Dependabot PRs 78 | run: gh pr merge --auto --merge "$PR_URL" 79 | env: 80 | PR_URL: ${{github.event.pull_request.html_url}} 81 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 82 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Axolotl main pipeline 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | uses: ./.github/workflows/build.yml 11 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Axolotl pull request pipeline 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | permissions: 11 | checks: write 12 | 13 | jobs: 14 | build: 15 | uses: ./.github/workflows/build.yml 16 | test: 17 | uses: ./.github/workflows/test.yml 18 | 19 | # Check if all important jobs passed 20 | # This can be used as required status for branch protection rules. 21 | pr-ok: 22 | runs-on: ubuntu-latest 23 | needs: 24 | - build 25 | - test 26 | if: always() 27 | steps: 28 | - name: All tests ok 29 | if: ${{ !(contains(needs.*.result, 'failure')) }} 30 | run: exit 0 31 | - name: Some tests failed 32 | if: ${{ contains(needs.*.result, 'failure') }} 33 | run: exit 1 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Axolotl release pipeline 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | uses: ./.github/workflows/build.yml 11 | 12 | release: 13 | name: Create release 14 | needs: build 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Download build artifacts 19 | uses: actions/download-artifact@v4 20 | with: 21 | path: build-artifacts 22 | - name: Get git tag version 23 | id: get_version 24 | uses: battila7/get-version-action@v2 25 | - name: Create draft GitHub release page 26 | id: create_release 27 | uses: marvinpinto/action-automatic-releases@v1.2.1 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | title: ${{ steps.get_version.outputs.version }} 31 | draft: true 32 | prerelease: false 33 | files: | 34 | **/*.click 35 | **/*.deb 36 | **/*.AppImage 37 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues and PRs 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | # Number of days of inactivity before a stale Issue or Pull Request is closed. 13 | # Set to -1 to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 14 | days-before-close: 14 15 | # Number of days of inactivity before an Issue or Pull Request becomes stale 16 | days-before-stale: 30 17 | # We don't want any Issues to be marked as stale for now. 18 | days-before-issue-stale: -1 19 | exempt-issue-labels: dependencies,no stalebot 20 | exempt-pr-labels: dependencies,no stalebot 21 | operations-per-run: 500 22 | stale-issue-label: stale 23 | stale-pr-label: stale 24 | stale-pr-message: > 25 | This pull request has been automatically marked as stale because it has not had 26 | activity in the last 30 days. It will be closed in 2 weeks if no further activity occurs. Please 27 | feel free to give a status update now, ping for review, or re-open when it's ready. 28 | Thank you for your contributions! 29 | close-pr-message: > 30 | This pull request has been automatically closed because it has not had 31 | activity in the last 2 weeks. Please feel free to give a status update now, ping for review, or re-open when it's ready. 32 | Thank you for your contributions! 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Axolotl test pipeline 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | env: 8 | NODE_VERSION: "20.x" 9 | 10 | jobs: 11 | test-axolotl-web: 12 | name: Test axolotl-web 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Setup Node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ env.NODE_VERSION }} 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v4 22 | 23 | - name: Download dependencies 24 | run: make download-dependencies-axolotl-web 25 | 26 | - name: Test 27 | run: make check-axolotl-web 28 | 29 | rustfmt: 30 | name: rustfmt 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | 36 | - name: Rust setup 37 | uses: actions-rust-lang/setup-rust-toolchain@v1 38 | with: 39 | toolchain: stable 40 | components: rustfmt 41 | rustflags: '' 42 | 43 | - name: Check code format 44 | uses: actions-rust-lang/rustfmt@v1 45 | 46 | clippy: 47 | name: clippy 48 | runs-on: ubuntu-latest 49 | permissions: 50 | checks: write 51 | steps: 52 | - name: Checkout repository 53 | uses: actions/checkout@v4 54 | 55 | - name: install protoc 56 | run: sudo apt-get install protobuf-compiler 57 | 58 | - name: Rust setup 59 | uses: actions-rust-lang/setup-rust-toolchain@v1 60 | with: 61 | toolchain: stable 62 | components: clippy 63 | rustflags: '' 64 | 65 | - name: Run clippy lints 66 | uses: auguwu/clippy-action@1.4.0 67 | with: 68 | token: ${{secrets.GITHUB_TOKEN}} 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | axolotl.nanuc_*click 2 | axolotl 3 | builddir/ 4 | build/ 5 | target/ 6 | .clickable/* 7 | *po~ 8 | *~~ 9 | *.snap 10 | *.AppImage 11 | *.vcf 12 | main 13 | 14 | output 15 | *.syso 16 | dist 17 | .flatpak-builder 18 | .flatpak 19 | .idea/* 20 | .idea 21 | axolotl.code-workspace 22 | libzkgroup.so 23 | libzkgroup*.so 24 | *.log 25 | *.sql* 26 | *.gz 27 | rustc-ice*.txt 28 | .devcontainer/ 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axolotl" 3 | version = "2.0.3" 4 | edition = "2021" 5 | description = """\ 6 | This is a cross-platform Signal client.""" 7 | license = "GPL-3" 8 | rust-version = "1.75" 9 | 10 | [build-dependencies] 11 | tauri-build = { version = "2.0.3", features = [], optional = true } 12 | 13 | [dependencies] 14 | tauri = { version = "2.1.1", features = [], optional = true } 15 | presage = { git = "https://github.com/nanu-c/presage", rev = "350921c533224265a0ff026dfddc67419ca45b7a" } 16 | presage-store-sled = { git = "https://github.com/nanu-c/presage", rev = "350921c533224265a0ff026dfddc67419ca45b7a" } 17 | #presage = {path = "../presage/presage"} 18 | #presage-store-sled = { path = "../presage/presage-store-sled" } 19 | zeroize = { version = "1.8.1", default-features = false } 20 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 21 | tokio-stream = "0.1" 22 | hex = "0.4" 23 | warp = "0.3" 24 | futures = { version = "0.3", default-features = false } 25 | serde = { version = "1.0", features = ["derive"] } 26 | serde_json = "1.0" 27 | log = "0.4.27" 28 | env_logger = "0.11.6" 29 | url = "2.5.4" 30 | sled = "0.34.7" 31 | clap = { version = "4.5.39", features = ["derive"] } 32 | dirs = "6.0.0" 33 | notify-rust = { version = "4.11.3", optional = true } 34 | data-url = "0.3.1" 35 | 36 | dbus = { version = "0.9", optional = true } 37 | 38 | [patch.crates-io] 39 | "curve25519-dalek" = { git = 'https://github.com/signalapp/curve25519-dalek', tag = 'signal-curve25519-4.0.0' } 40 | 41 | 42 | [features] 43 | # by default Tauri runs in production mode 44 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 45 | # this feature is used for production builds where `devPath` points to the filesystem 46 | # DO NOT remove this 47 | default = ["dep:notify-rust"] 48 | custom-protocol = ["tauri/custom-protocol"] 49 | tauri = ["dep:tauri", "dep:notify-rust", "custom-protocol"] 50 | ut = ["dep:dbus"] 51 | # Use this feature to debug registration issues on test servers 52 | staging-servers = [] 53 | 54 | 55 | [[example]] 56 | name = "dump_db" 57 | path = "examples/dump/dump_db.rs" 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This is the Makefile for axolotl. 2 | # For more info about the syntax, see https://makefiletutorial.com/ 3 | 4 | .PHONY: build build-axolotl-web build-axolotl install install-axolotl uninstall uninstall-axolotl clean clean-axolotl-web clean-axolotl check check-axolotl-web check-axolotl build-translation download-dependencies download-dependencies-axolotl-web download-dependencies-axolotl update-version download-dependencies-flatpak install-flatpak build-snap install-snap 5 | 6 | AXOLOTL_GIT_VERSION := $(shell git tag | tail --lines=1) 7 | AXOLOTL_VERSION := $(subst v,,$(AXOLOTL_GIT_VERSION)) 8 | ARCH := $(shell uname --machine) 9 | CURRENT_DIR := $(shell pwd) 10 | 11 | APP_ID := axolotl 12 | define APPDATA_TEXT= 13 | \\t\t\n\ 14 | \t\t\t\thttps://github.com/nanu-c/axolotl/releases/tag/v$(NEW_VERSION)\n\ 15 | \t\t 16 | endef 17 | export APPDATA_TEXT 18 | 19 | YARN := $(shell which yarn 2>/dev/null) 20 | CARGO := $(shell which cargo 2>/dev/null) 21 | FLATPAK := $(shell which flatpak 2>/dev/null) 22 | FLATPAK_BUILDER := $(shell which flatpak-builder 2>/dev/null) 23 | SNAPCRAFT := $(shell which snapcraft 2>/dev/null) 24 | SNAP := $(shell which snap 2>/dev/null) 25 | 26 | # See common variable names: https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html 27 | PREFIX := "/usr" 28 | BINDIR := $(PREFIX)/bin 29 | LIBDIR := $(PREFIX)/lib 30 | DATADIR := $(PREFIX)/share 31 | CARGO_PREFIX := ${HOME}/.cargo/bin 32 | 33 | all: build 34 | 35 | build: build-axolotl-web build-axolotl 36 | 37 | install: install-axolotl install-metadata 38 | 39 | uninstall: uninstall-axolotl 40 | 41 | clean: clean-axolotl-web clean-axolotl 42 | 43 | check: check-axolotl-web check-axolotl 44 | 45 | download-dependencies: download-dependencies-axolotl-web download-dependencies-axolotl 46 | 47 | # axolotl 48 | download-dependencies-axolotl: Cargo.toml Cargo.lock 49 | $(CARGO) fetch --verbose 50 | 51 | build-axolotl: download-dependencies-axolotl 52 | @echo "Building axolotl..." 53 | $(CARGO) build --features tauri --release --verbose 54 | 55 | install-axolotl: build-axolotl 56 | @echo "Installing axolotl..." 57 | @install -D -m 755 $(CURRENT_DIR)/target/release/axolotl $(DESTDIR)$(BINDIR)/axolotl/axolotl 58 | 59 | uninstall-axolotl: 60 | @echo "Uninstalling axolotl..." 61 | @rm -rf $(DESTDIR)$(BINDIR)/axolotl 62 | 63 | clean-axolotl: 64 | rm -f $(CURRENT_DIR)/target 65 | 66 | # axolotl-web 67 | download-dependencies-axolotl-web: axolotl-web/package.json axolotl-web/yarn.lock 68 | $(YARN) --cwd axolotl-web install --frozen-lockfile 69 | 70 | build-axolotl-web: download-dependencies-axolotl-web 71 | @echo "Building axolotl-web..." 72 | $(YARN) --cwd axolotl-web run build 73 | 74 | check-axolotl-web: 75 | $(YARN) --cwd axolotl-web run depcheck 76 | $(YARN) --cwd axolotl-web run lint 77 | $(YARN) --cwd axolotl-web run test 78 | 79 | clean-axolotl-web: 80 | rm -rf $(CURRENT_DIR)/axolotl-web/dist 81 | 82 | # utilities 83 | build-translation: 84 | $(YARN) --cwd axolotl-web run translate 85 | 86 | update-version: 87 | ifeq ($(NEW_VERSION),) 88 | @echo 'Please specify the new version to use! Example: "make update-version NEW_VERSION=0.9.10"' 89 | else 90 | @echo "Replacing current version $(AXOLOTL_VERSION) with new version $(NEW_VERSION)" 91 | @sed -i 's/$(AXOLOTL_VERSION)/$(NEW_VERSION)/' manifest.json 92 | @sed -i 's/$(AXOLOTL_VERSION)/$(NEW_VERSION)/' snap/snapcraft.yaml 93 | @sed -i 's/$(AXOLOTL_VERSION)/$(NEW_VERSION)/' docs/INSTALL.md 94 | @sed -i "32i $$APPDATA_TEXT" data/axolotl.metainfo.xml 95 | @echo "Update complete" 96 | endif 97 | 98 | install-metadata: 99 | @install -D -m 644 data/icons/icon.png $(DESTDIR)$(DATADIR)/icons/hicolor/128x128/apps/$(APP_ID).png 100 | @install -D -m 644 data/axolotl.metainfo.xml $(DESTDIR)$(DATADIR)/metainfo/$(APP_ID).metainfo.xml 101 | @sed -i 's/@app-id@/$(APP_ID)/' $(DESTDIR)$(DATADIR)/metainfo/$(APP_ID).metainfo.xml 102 | @install -D -m 644 data/axolotl.desktop $(DESTDIR)$(DATADIR)/applications/$(APP_ID).desktop 103 | @sed -i 's/@icon@/$(APP_ID).png/' $(DESTDIR)$(DATADIR)/applications/$(APP_ID).desktop 104 | 105 | # Flatpak 106 | download-dependencies-flatpak: 107 | $(FLATPAK) install org.gnome.Platform//45 108 | $(FLATPAK) install org.gnome.Sdk//45 109 | $(FLATPAK) install org.freedesktop.Sdk.Extension.node18//22.08 110 | $(FLATPAK) install org.freedesktop.Sdk.Extension.rust-stable//22.08 111 | 112 | build-flatpak: 113 | $(FLATPAK_BUILDER) flatpak/build --force-clean --ccache flatpak/org.nanuc.Axolotl.yml 114 | 115 | install-flatpak: 116 | $(FLATPAK_BUILDER) flatpak/build --force-clean --user --install flatpak/org.nanuc.Axolotl.yml 117 | 118 | debug-flatpak: 119 | $(FLATPAK_BUILDER) flatpak/build --run --verbose flatpak/org.nanuc.Axolotl.yml sh 120 | 121 | uninstall-flatpak: 122 | $(FLATPAK) uninstall org.nanuc.Axolotl 123 | 124 | clean-flatpak: 125 | rm -rf flatpak/.flatpak-builder flatpak/build 126 | 127 | # Snap 128 | build-snap: 129 | @sudo $(SNAPCRAFT) 130 | 131 | install-snap: 132 | @sudo $(SNAP) install axolotl_$(AXOLOTL_VERSION)_amd64.snap --dangerous 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axolotl 2 | 3 | Axolotl is a complete cross-platform [Signal](https://www.signal.org) client, compatible with the Ubuntu Phone and more. 4 | Unlike the desktop Signal client, **Axolotl is completely autonomous** and doesn't require you to have created an 5 | account with the official Signal application. 6 | 7 | It is built upon [presage](https://github.com/whisperfish/presage) and a VueJs frontend that runs in a [tauri](https://tauri.app/) or a qml WebEngineView container. 8 | 9 |

10 | 11 | Screenshot of axolotl 12 | 13 |

14 | 15 | ## Features 16 | 17 | * Phone registration 18 | * Direct messages 19 | * Group messages *mostly* 20 | * Photo, video, audio and contact attachments in both direct and group mode 21 | * Preview for photo and audio attachments 22 | * Storing conversations 23 | * Encrypted message store 24 | * Desktop client provisioning/syncing *partially* 25 | 26 | ## Broken: 27 | * Contact discovery 28 | 29 | 30 | There are still bugs and UI/UX quirks. 31 | 32 | ## Installation 33 | 34 | Axolotl can be installed through different means. 35 | 36 | | Package | Maintainer | Comment | 37 | | ------- | ---------- | -------- | 38 | | Get it from the OpenStore | nanu-c | For Ubuntu Touch | 39 | | Get it from the Snap Store | nanu-c | For Ubuntu desktop | 40 | | Download on Flathub | olof-nord | https://github.com/flathub/org.nanuc.Axolotl | 41 | | mobian version | nanu-c | https://github.com/nanu-c/axolotl/releases
(Debian package in the Assets section)| 42 | 43 | ## Building 44 | 45 | To find out how to build from source and install yourself, please see below. 46 | 47 | * with Clickable: see [here](docs/INSTALL.md#clickable). 48 | * with Snap: see [here](docs/INSTALL.md#snap). 49 | * with Flatpak: see [here](docs/INSTALL.md#flatpak). 50 | * with AppImage: see [here](docs/INSTALL.md#appimage). 51 | * for Debian: see [here](docs/INSTALL.md#debian). 52 | * manually: see [here](docs/INSTALL.md#bare). 53 | 54 | ## Run flags 55 | 56 | - `--mode` 57 | - `tauri`: Default GUI 58 | - `ubuntu-touch`: Ubuntu Touch GUI 59 | - `daemon`: Headless 60 | 61 | ## Contributing 62 | 63 | * Please fill issues here on github https://github.com/nanu-c/axolotl/issues 64 | * Help translate Axolotl to your language(s). For information how to translate, please see [TRANSLATE.md](docs/TRANSLATE.md). 65 | * Contribute code by making PR's (pull requests) 66 | 67 | If you contribute new strings, please: 68 | 69 | - make them translatable 70 | - avoid linebreaks within one tag, that will break extracting the strings for translation 71 | - try to reduce formatting tags within translatable strings 72 | 73 | Translation is done by using the `easygettext` module. Detailed instructions how strings are made translatable are given here: [https://www.npmjs.com/package/easygettext](https://www.npmjs.com/package/easygettext). 74 | 75 | In short words, either use the `v-translate` keyword in the last tag enclosing the string or wrap your string in a `` tag as the last tag. 76 | If you need to make strings in the script section translatable, do it like this `this.$gettext("string")`. 77 | 78 | When adding new translatable strings with a PR, make sure to extract and update commands as instructed [here](docs/TRANSLATE.md). Then also commit the updated pot and po files containing the new strings. 79 | 80 | examples: 81 | 82 | - `

Translate me!

` instead of `

Translate me!

` 83 | - `

Translate me!

` instead of `

Translate me!

` 84 | - `

Translate me!


Please...

` instead of `

Translate me!
Please...

` 85 | - `
Yes, I am translatable!
` instead of `
No, I am not translatable!
` 86 | - `
This is a free and open source Signal client written in golang and vuejs.
` 87 | - in \ part: `this.cMTitle = this.$gettext("I am a translatable title!");` 88 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We are currently only supporting the latest version of axolotl 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please send an e-mail to aaron@nanu-c.org explaining the vulnerability. 10 | We as the devleopers of axolotl do our best to our knowledge to avoid vulnerabilities but we also make mistakes. 11 | Axolotl run only by volunteers, so don't expect much in exchange, expect for gratful thanks in change log :). 12 | You can always ask for some axolotl stickers. 13 | -------------------------------------------------------------------------------- /axolotl-web/.depcheckrc.yaml: -------------------------------------------------------------------------------- 1 | ignores: 2 | - "sass-loader" 3 | - "bootstrap" 4 | - "@vue/cli-plugin-eslint" 5 | - "@babel/eslint-parser" 6 | - "@vue/cli-plugin-babel" 7 | - "browserslist" 8 | - "vue3-gettext" 9 | - "@vue/eslint-config-airbnb" 10 | - "eslint-config-prettier" 11 | - "eslint-plugin-import" 12 | - "eslint-plugin-prettier" 13 | - "eslint-plugin-prettier-vue" 14 | - "prettier" 15 | - "sass" 16 | - "@vue/cli-plugin-unit-mocha" 17 | - "@vue/compiler-sfc" 18 | - "autoprefixer" 19 | specials: 20 | - "bin" 21 | - "eslint" 22 | skip-missing: true 23 | -------------------------------------------------------------------------------- /axolotl-web/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.*] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 -------------------------------------------------------------------------------- /axolotl-web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | /*.so -------------------------------------------------------------------------------- /axolotl-web/.nvmrc: -------------------------------------------------------------------------------- 1 | 18.12.0 2 | -------------------------------------------------------------------------------- /axolotl-web/README.md: -------------------------------------------------------------------------------- 1 | # axolotl-web 2 | 3 | This is the frontend half of the axolotl project. 4 | Axolotl is a complete cross-platform Signal client. 5 | 6 | The Axolotl backend is running a web server, and with it serving the frontend bundle. 7 | 8 | ## Setup 9 | 10 | This (sub)project is set up to support Node Version Manager (nvm). 11 | To install, see [here](https://github.com/nvm-sh/nvm#installing-and-updating). 12 | 13 | Once installed, the node and yarn version used by this project can be installed as follows. 14 | 15 | ``` 16 | nvm install 17 | nvm use 18 | ``` 19 | 20 | Lastly, the dependencies needs to be downloaded. 21 | 22 | ``` 23 | yarn install 24 | ``` 25 | 26 | ## Run 27 | 28 | To start just the frontend, use the following command. 29 | 30 | Note though, that the intended use of the frontend is generally to be started and used by the backend. 31 | 32 | ``` 33 | yarn run serve 34 | ``` 35 | 36 | ## Build 37 | 38 | To create the bundle, which the backend is serving, a bundle is required. 39 | The bundle contains HTML, javascript and CSS - see `axolotl-web/dist` once finished. 40 | 41 | ``` 42 | yarn run build 43 | ``` 44 | -------------------------------------------------------------------------------- /axolotl-web/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { includeIgnoreFile } from "@eslint/compat"; 2 | import pluginVue from 'eslint-plugin-vue'; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | const gitignorePath = path.resolve(__dirname, ".gitignore"); 9 | 10 | export default [ 11 | ...pluginVue.configs['flat/recommended'], 12 | includeIgnoreFile(gitignorePath), 13 | { 14 | languageOptions: { 15 | globals: {}, 16 | }, 17 | 18 | files: ["**/*.js", "**/*.ts", "**/*.vue"], 19 | 20 | rules: { 21 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 22 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", 23 | "comma-dangle": "off", 24 | "class-methods-use-this": "off", 25 | "import/no-unresolved": "off", 26 | "import/extensions": "off", 27 | "implicit-arrow-linebreak": "off", 28 | "import/prefer-default-export": "off", 29 | "vue/no-mutating-props": "off", 30 | "vue/singleline-html-element-content-newline": "off", 31 | "vue/max-attributes-per-line": "off", 32 | "vue/component-name-in-template-casing": "off", 33 | 34 | "vue/html-self-closing": ["error", { 35 | html: { 36 | void: "any", 37 | normal: "always", 38 | component: "always", 39 | }, 40 | 41 | svg: "always", 42 | math: "always", 43 | }], 44 | }, 45 | }]; 46 | -------------------------------------------------------------------------------- /axolotl-web/gettext.config.cjs: -------------------------------------------------------------------------------- 1 | // This is the gettext config file. 2 | // See here for documentation: https://jshmrtn.github.io/vue3-gettext/extraction.html 3 | 4 | module.exports = { 5 | input: { 6 | path: "./src", 7 | include: ["**/*.vue"], 8 | }, 9 | output: { 10 | path: "../po", 11 | potPath: "./axolotl.nanuc.pot", 12 | jsonPath: "../axolotl-web/translations/translations.json", 13 | locales: [ 14 | "ar", 15 | "be", 16 | "bg", 17 | "cs", 18 | "da", 19 | "de", 20 | "el", 21 | "es", 22 | "fa", 23 | "fi", 24 | "fr", 25 | "hr", 26 | "hu", 27 | "in", 28 | "it", 29 | "iw", 30 | "ja", 31 | "kn-rIN", 32 | "ko", 33 | "mk", 34 | "nb", 35 | "nl", 36 | "no", 37 | "pl", 38 | "pt-BR", 39 | "pt", 40 | "ro", 41 | "ru", 42 | "sk", 43 | "sl", 44 | "sr", 45 | "sv", 46 | "ta", 47 | "tr", 48 | "vi", 49 | "zh-rCN", 50 | ], 51 | flat: true, 52 | linguas: true, 53 | silent: true, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /axolotl-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | axolotl-web 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /axolotl-web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"] 3 | } 4 | -------------------------------------------------------------------------------- /axolotl-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axolotl-web", 3 | "version": "2.0.3", 4 | "private": true, 5 | "type": "module", 6 | "description": "This is the frontend half of the axolotl project. Axolotl is a complete cross-platform Signal client.", 7 | "author": { 8 | "name": "nanu-c", 9 | "url": "https://github.com/nanu-c" 10 | }, 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vite build", 14 | "serve": "vite", 15 | "preview": "vite preview", 16 | "test": "vitest run", 17 | "lint": "eslint --fix src", 18 | "depcheck": "depcheck", 19 | "translate": "yarn run translate-extract && yarn run translate-compile", 20 | "translate-compile": "vue-gettext-compile", 21 | "translate-extract": "vue-gettext-extract" 22 | }, 23 | "main": "src/main.js", 24 | "dependencies": { 25 | "@fortawesome/fontawesome-svg-core": "^6.7.2", 26 | "@fortawesome/free-solid-svg-icons": "^6.7.2", 27 | "@fortawesome/vue-fontawesome": "^3.0.8", 28 | "@jmd01/mic-recorder-to-mp3": "^2.2.3", 29 | "@popperjs/core": "^2.11.8", 30 | "bootstrap": "^5.3.6", 31 | "core-js": "^3.42.0", 32 | "file-saver": "^2.0.5", 33 | "linkify-html": "^4.3.0", 34 | "linkifyjs": "^4.3.1", 35 | "moment": "^2.30.1", 36 | "qrcode": "1.5.4", 37 | "vue": "^3.5.16", 38 | "vue-native-websocket-vue3": "^3.1.8", 39 | "vue-router": "^4.5.1", 40 | "vue3-gettext": "^4.0.0-alpha.8", 41 | "vue3-tel-input": "^1.0.4", 42 | "vuex": "^4.1.0", 43 | "webrtc-adapter": "^9.0.3" 44 | }, 45 | "devDependencies": { 46 | "@eslint/compat": "^1.2.9", 47 | "@vitejs/plugin-vue": "^5.2.4", 48 | "@vue/compiler-sfc": "^3.5.15", 49 | "@vue/eslint-config-airbnb": "^8.0.0", 50 | "@vue/test-utils": "^2.4.6", 51 | "browserslist": "^4.25.0", 52 | "chai": "^5.2.0", 53 | "depcheck": "^1.4.7", 54 | "eslint": "^9.28.0", 55 | "eslint-config-prettier": "^10.1.5", 56 | "eslint-plugin-import": "^2.31.0", 57 | "eslint-plugin-prettier": "^5.4.1", 58 | "eslint-plugin-prettier-vue": "^5.0.0", 59 | "eslint-plugin-vue": "^9.33.0", 60 | "jsdom": "^26.1.0", 61 | "prettier": "^3.5.3", 62 | "sass": "^1.89.1", 63 | "vite": "^6.3.5", 64 | "vitest": "^3.2.1" 65 | }, 66 | "browserslist": [ 67 | "> 1%", 68 | "last 2 versions", 69 | "chrome 69" 70 | ], 71 | "//": [ 72 | "the support for chrome version 69 ensures support", 73 | "for the chromium version used by QtWebEngine 5.12." 74 | ], 75 | "bugs": { 76 | "url": "https://github.com/nanu-c/axolotl/issues" 77 | }, 78 | "engines": { 79 | "node": ">= 20.0.0" 80 | }, 81 | "homepage": "https://axolotl.chat", 82 | "keywords": [ 83 | "signal", 84 | "messaging", 85 | "vue" 86 | ], 87 | "license": "GPL-3.0", 88 | "repository": { 89 | "type": "git", 90 | "url": "https://github.com/nanu-c/axolotl.git" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /axolotl-web/public/axolotl.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/public/axolotl.ico -------------------------------------------------------------------------------- /axolotl-web/public/axolotl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/public/axolotl.png -------------------------------------------------------------------------------- /axolotl-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/public/favicon.ico -------------------------------------------------------------------------------- /axolotl-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 70 | 71 | 117 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/dark.scss: -------------------------------------------------------------------------------- 1 | $primary: #2090ea; 2 | $hover-bg: #444; 3 | 4 | // Bootstrap variables 5 | $body-bg: #000; 6 | $body-color: #ccc; 7 | 8 | $input-bg: #999; 9 | $input-border-color: #999; 10 | $input-color: #000; 11 | $input-plaintext-color: #000; 12 | $input-placeholder-color: #555; 13 | $input-focus-bg: #bbb; 14 | $input-focus-border-color: $primary; 15 | $input-border-width: 2px; 16 | $input-box-shadow: unset; 17 | $input-focus-box-shadow: unset; 18 | 19 | $chat-entry-border-color: #333; 20 | 21 | $message-color: #fff; 22 | $message-metadata-color: #eee; 23 | $incoming-message-bg: #2880b0; 24 | $outgoing-message-bg: #29b786; 25 | $sent-message-link: #004090; 26 | $sent-message-link-visited: #003060; 27 | 28 | $message-input-border: $body-bg; 29 | 30 | $sending-url: "sending_dark.svg"; 31 | $sent-url: "check-circle-outline_dark.svg"; 32 | $delivered-url: "double-check_dark.svg"; 33 | $read-url: "double-check-filled_dark.svg"; 34 | 35 | @forward 'style.scss' with ( 36 | $primary: $primary, 37 | $hover-bg: $hover-bg, 38 | $body-bg: $body-bg, 39 | $body-color: $body-color, 40 | $input-bg: $input-bg, 41 | $input-border-color: $input-border-color, 42 | $input-color: $input-color, 43 | $input-plaintext-color: $input-plaintext-color, 44 | $input-placeholder-color: $input-placeholder-color, 45 | $input-focus-bg: $input-focus-bg, 46 | $input-focus-border-color: $input-focus-border-color, 47 | $input-border-width: $input-border-width, 48 | $input-box-shadow: $input-box-shadow, 49 | $input-focus-box-shadow: $input-focus-box-shadow, 50 | $chat-entry-border-color: $chat-entry-border-color, 51 | $message-color: $message-color, 52 | $message-metadata-color: $message-metadata-color, 53 | $incoming-message-bg: $incoming-message-bg, 54 | $outgoing-message-bg: $outgoing-message-bg, 55 | $sent-message-link: $sent-message-link, 56 | $sent-message-link-visited: $sent-message-link-visited, 57 | $message-input-border: $message-input-border, 58 | $sending-url: $sending-url, 59 | $sent-url: $sent-url, 60 | $delivered-url: $delivered-url, 61 | $read-url: $read-url, 62 | ); 63 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/fonts/ubuntu-r-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/src/assets/fonts/ubuntu-r-webfont.woff -------------------------------------------------------------------------------- /axolotl-web/src/assets/fonts/ubuntu-r-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/src/assets/fonts/ubuntu-r-webfont.woff2 -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/check-circle-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/check-circle-outline_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/double-check-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/double-check-filled_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/double-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/double-check_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/sending.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/sending_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/images/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/light.scss: -------------------------------------------------------------------------------- 1 | $primary: #2090ea; 2 | $body-bg: #fff; 3 | $hover-bg: #eee; 4 | $body-color: #333; 5 | 6 | $input-bg: #fff; 7 | 8 | $chat-entry-border-color: #ddd; 9 | 10 | $message-color: #000; 11 | $message-metadata-color: #333; 12 | $incoming-message-bg: #92c4ec; 13 | $outgoing-message-bg: #9ae9a9; 14 | $sent-message-bg: #d3f2d7; 15 | $sent-message-link: $primary; 16 | 17 | $message-input-border: #2090ea; 18 | 19 | $sending-url: "sending.svg"; 20 | $sent-url: "check-circle-outline.svg"; 21 | $delivered-url: "double-check.svg"; 22 | $read-url: "double-check-filled.svg"; 23 | 24 | @forward 'style.scss' with ( 25 | $primary: $primary, 26 | $body-bg: $body-bg, 27 | $hover-bg: $hover-bg, 28 | $body-color: $body-color, 29 | $input-bg: $input-bg, 30 | $chat-entry-border-color: $chat-entry-border-color, 31 | $message-color: $message-color, 32 | $message-metadata-color: $message-metadata-color, 33 | $incoming-message-bg: $incoming-message-bg, 34 | $outgoing-message-bg: $outgoing-message-bg, 35 | $sent-message-bg: $sent-message-bg, 36 | $sent-message-link: $sent-message-link, 37 | $message-input-border: $message-input-border, 38 | $sending-url: $sending-url, 39 | $sent-url: $sent-url, 40 | $delivered-url: $delivered-url, 41 | $read-url: $read-url, 42 | ); 43 | -------------------------------------------------------------------------------- /axolotl-web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/axolotl-web/src/assets/logo.png -------------------------------------------------------------------------------- /axolotl-web/src/assets/style.scss: -------------------------------------------------------------------------------- 1 | // Start of default variables 2 | $body-bg: "" !default; 3 | $body-color: "" !default; 4 | $chat-entry-border-color: "" !default; 5 | $delivered-url: "" !default; 6 | $hover-bg: "" !default; 7 | $incoming-message-bg: "" !default; 8 | $input-bg: "" !default; 9 | $input-border-color: "" !default; 10 | $input-border-width: "" !default; 11 | $input-box-shadow: "" !default; 12 | $input-color: "" !default; 13 | $input-focus-bg: "" !default; 14 | $input-focus-border-color: "" !default; 15 | $input-focus-box-shadow: "" !default; 16 | $input-placeholder-color: "" !default; 17 | $input-plaintext-color: "" !default; 18 | $message-color: "" !default; 19 | $message-input-border: "" !default; 20 | $message-metadata-color: "" !default; 21 | $outgoing-message-bg: "" !default; 22 | $primary: "" !default; 23 | $read-url: "" !default; 24 | $sending-url: "" !default; 25 | $sent-message-bg: "" !default; 26 | $sent-message-link-visited: "" !default; 27 | $sent-message-link: "" !default; 28 | $sent-url: "" !default; 29 | // End of default variables 30 | 31 | @use "sass:color"; 32 | $sent-message-link-visited: color.adjust($sent-message-link, $lightness: -10%); 33 | $default-radius: 5px; 34 | 35 | // Start bootstrap override 36 | $dropdown-bg: $body-bg; 37 | $dropdown-link-hover-bg: $hover-bg; 38 | $dropdown-color: $body-color; 39 | $dropdown-link-color: $body-color; 40 | $dropdown-link-hover-color: $body-color; 41 | $dropdown-border-color: $body-color; 42 | 43 | $modal-content-color: $body-color; 44 | $modal-content-bg: $body-bg; 45 | $modal-content-border-color: $body-color; 46 | $close-color: $body-color; 47 | 48 | $link-color: $primary; 49 | // End of bootstrap override 50 | 51 | @use "bootstrap/scss/bootstrap"; 52 | 53 | // All variables are now loaded, we can use them 54 | .row.chat-entry { 55 | border-bottom: 1px solid $chat-entry-border-color; 56 | } 57 | 58 | .message { 59 | color: $message-color; 60 | a { 61 | color: $sent-message-link; 62 | &:visited { 63 | color: $sent-message-link-visited; 64 | } 65 | } 66 | } 67 | 68 | .outgoing .message { 69 | background-color: $outgoing-message-bg; 70 | } 71 | .incoming .message { 72 | background-color: $incoming-message-bg; 73 | } 74 | .message .meta { 75 | color: $message-metadata-color; 76 | } 77 | 78 | .transfer-indicator { 79 | background-image: url("../assets/images/" + $sending-url); 80 | } 81 | .sent .transfer-indicator { 82 | background-image: url("../assets/images/" + $sent-url); 83 | } 84 | .delivered .transfer-indicator { 85 | background-image: url("../assets/images/" + $delivered-url); 86 | } 87 | .read .transfer-indicator { 88 | background-image: url("../assets/images/" + $read-url); 89 | } 90 | 91 | .messageInputBox { 92 | background-color: $body-bg; 93 | } 94 | 95 | #messageInput { 96 | border: 1px solid $message-input-border; 97 | background-color: $input-bg; 98 | } 99 | 100 | .vue-phone-number-input { 101 | background-color: $input-bg; 102 | color: $input-color; 103 | } 104 | 105 | .info { 106 | background-color: $body-bg; 107 | } 108 | 109 | .warning-box { 110 | position: relative; 111 | color: #000; 112 | border: solid 2px #f80; 113 | border-radius: $default-radius; 114 | background-color: #f80a; 115 | padding: 0.5rem 0.8rem; 116 | font-size: 0.9rem; 117 | } 118 | 119 | .close-warning-box { 120 | position: absolute; 121 | top: 0px; 122 | right: 5px; 123 | font-size: 20px; 124 | color: #e60; 125 | font-weight: bold; 126 | } 127 | 128 | .modal { 129 | background: #000e; 130 | } 131 | 132 | @font-face { 133 | font-family: 'ubuntu'; 134 | src: url('fonts/ubuntu-r-webfont.woff2') format('woff2'), 135 | url('fonts/ubuntu-r-webfont.woff') format('woff'); 136 | font-weight: normal; 137 | font-style: normal; 138 | 139 | } 140 | 141 | body{ 142 | font-size:14px; 143 | } -------------------------------------------------------------------------------- /axolotl-web/src/components/AddContactModal.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 84 | 105 | -------------------------------------------------------------------------------- /axolotl-web/src/components/AddDeviceModal.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 53 | 74 | -------------------------------------------------------------------------------- /axolotl-web/src/components/AddGroupMembersModal.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 150 | 151 | 152 | 232 | -------------------------------------------------------------------------------- /axolotl-web/src/components/AttachmentBar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 41 | 79 | -------------------------------------------------------------------------------- /axolotl-web/src/components/ConfirmationModal.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 45 | 73 | -------------------------------------------------------------------------------- /axolotl-web/src/components/EditContactModal.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 73 | 94 | -------------------------------------------------------------------------------- /axolotl-web/src/components/ErrorModal.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 72 | 90 | -------------------------------------------------------------------------------- /axolotl-web/src/components/IdentityModal.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 69 | 105 | -------------------------------------------------------------------------------- /axolotl-web/src/components/ImportUnavailableModal.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 50 | 51 | 59 | -------------------------------------------------------------------------------- /axolotl-web/src/components/ImportVcfModal.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 105 | 127 | -------------------------------------------------------------------------------- /axolotl-web/src/components/VerificationPinInput.vue: -------------------------------------------------------------------------------- 1 | 24 | 196 | 237 | -------------------------------------------------------------------------------- /axolotl-web/src/config.js: -------------------------------------------------------------------------------- 1 | 2 | export const config = { 3 | primaryRegistration: false, 4 | secondaryRegistration: true, 5 | contacts: false, 6 | }; 7 | export default config; -------------------------------------------------------------------------------- /axolotl-web/src/helpers/uuidCheck.js: -------------------------------------------------------------------------------- 1 | const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 2 | const isValidV4UUID = uuid => uuidV4Regex.test(uuid); 3 | export const validateUUID = function (uuid) { 4 | var result = isValidV4UUID(uuid) 5 | return result 6 | } 7 | -------------------------------------------------------------------------------- /axolotl-web/src/layouts/Default.vue: -------------------------------------------------------------------------------- 1 | 48 | 69 | -------------------------------------------------------------------------------- /axolotl-web/src/layouts/Legacy.vue: -------------------------------------------------------------------------------- 1 | 14 | 23 | -------------------------------------------------------------------------------- /axolotl-web/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import VueNativeSock from 'vue-native-websocket-vue3' 4 | import store from './store/store' 5 | import { router } from "./router/router"; 6 | import { createGettext } from "vue3-gettext"; 7 | import translations from '../translations/translations.json' 8 | import { library } from '@fortawesome/fontawesome-svg-core' 9 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 10 | import linkifyHTML from 'linkify-html' 11 | import 'bootstrap'; 12 | 13 | import { 14 | faArrowDown, 15 | faArrowLeft, 16 | faCheck, 17 | faEllipsisV, 18 | faHeart, 19 | faMicrophone, 20 | faPause, 21 | faPaperPlane, 22 | faPencilAlt, 23 | faPlay, 24 | faPlus, 25 | faSearch, 26 | faStopCircle, 27 | faTimes, 28 | faTrash, 29 | faUserFriends, 30 | faVolumeMute, 31 | faWrench, 32 | } from '@fortawesome/free-solid-svg-icons' 33 | 34 | library.add(faArrowLeft, faEllipsisV, faPencilAlt, faPlus, faTrash, faPaperPlane, 35 | faUserFriends, faTimes, faCheck, faVolumeMute, faHeart, faSearch, faArrowDown, 36 | faMicrophone, faStopCircle, faPlay, faPause, faWrench 37 | ) 38 | const app = createApp(App) 39 | app.component('FontAwesomeIcon', FontAwesomeIcon) 40 | app.mixin({ 41 | methods: { 42 | linkify(content) { 43 | return linkifyHTML( 44 | content, 45 | { 46 | defaultProtocol: 'https', 47 | rel: { 48 | url: 'noopener noreferrer' 49 | }, 50 | target: { 51 | url: '_blank' 52 | }, 53 | className: 'linkified', 54 | ignoreTags: [ 55 | 'script', 56 | 'style' 57 | ] 58 | } 59 | ); 60 | }, 61 | }, 62 | }) 63 | const gettext = createGettext({ 64 | defaultLanguage: "en", 65 | translations, 66 | }); 67 | app.use(gettext); 68 | app.config.productionTip = false 69 | 70 | // set backend adress 71 | var websocketAdress = "ws://"; 72 | if (window.location.protocol === "https:") { 73 | websocketAdress = "wss://"; 74 | } 75 | websocketAdress += window.location.host; 76 | websocketAdress += "/ws"; 77 | 78 | // if (process.env.NODE_ENV === "development") { 79 | // console.log(process.env) 80 | // if (process.env.VITE_WS_ADDRESS) { 81 | // websocketAdress = 'ws://' + process.env.VITE_WS_ADDRESS + ':9080/ws'; 82 | // } else { 83 | // websocketAdress = 'ws://localhost:9080/ws'; 84 | // } 85 | // } 86 | websocketAdress = 'ws://localhost:9080/ws'; 87 | 88 | // initialise connection to the backend 89 | app.use(VueNativeSock, websocketAdress, 90 | { 91 | store: store, 92 | // format: 'json', 93 | reconnection: true, // (Boolean) whether to reconnect automatically (false) 94 | // reconnectionAttempts: 5, // (Number) number of reconnection attempts before giving up (Infinity), 95 | reconnectionDelay: 3000, // (Number) how long to initially wait before attempting a new (1000) } 96 | } 97 | ) 98 | app.use(store) 99 | app.use(router) 100 | app.mount('#app') 101 | 102 | export default app 103 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 62 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Debug.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 52 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/DeviceLinking.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 45 | 51 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/DeviceList.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 101 | 120 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/EditContact.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 59 | 60 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/EditGroup.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 121 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/NewGroup.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 106 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/OnBoarding.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 103 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Password.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 57 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Profile.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 91 | 92 | 142 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Register.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 76 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/SetPassword.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 111 | 127 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/SetUsername.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 55 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Settings.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 129 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /axolotl-web/src/pages/Verification.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 78 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /axolotl-web/src/router/router.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import store from '@/store/store' 3 | import Legacy from '@/layouts/Legacy.vue'; 4 | import Default from '@/layouts/Default.vue'; 5 | 6 | 7 | export const router = new createRouter({ 8 | history: createWebHistory(), 9 | routes: [ 10 | { 11 | path: "/", 12 | alias: "/chatList", 13 | name: "chatList", 14 | meta: { 15 | layout: Default, 16 | hasMenu: true, 17 | hasBackButton: false, 18 | }, 19 | component: () => import("@/pages/ChatList.vue") 20 | }, 21 | { 22 | path: "/chat/:id", 23 | name: "chat", 24 | meta: { 25 | layout: Legacy, 26 | }, 27 | props: route => ({ 28 | chatId: route.params.id, 29 | }), 30 | component: () => import("@/pages/MessageList.vue") 31 | }, 32 | { 33 | path: "/profile/:id", 34 | name: "profile", 35 | meta: { 36 | layout: Default, 37 | hasMenu: true, 38 | hasBackButton: true, 39 | }, 40 | props: route => ({ 41 | profileId: route.params.id, 42 | }), 43 | component: () => import("@/pages/Profile.vue") 44 | }, 45 | { 46 | path: "/editContact", 47 | name: "editContact", 48 | meta: { 49 | layout: Default, 50 | hasBackButton: true, 51 | }, 52 | component: () => import("@/pages/EditContact.vue") 53 | }, 54 | { 55 | // register page is where the registration starts 56 | path: "/register", 57 | name: "register", 58 | meta: { 59 | layout: Default, 60 | textCenter: true, 61 | hasBackButton: true, 62 | }, 63 | component: () => import("@/pages/Register.vue") 64 | }, 65 | { 66 | // verify page is for entering the sms pin 67 | path: "/verify", 68 | name: "verify", 69 | meta: { 70 | layout: Legacy, 71 | }, 72 | component: () => import("@/pages/Verification.vue") 73 | }, 74 | { 75 | // password page is for entering the password for database decryption 76 | path: "/password", 77 | name: "password", 78 | meta: { 79 | layout: Legacy, 80 | }, 81 | component: () => import("@/pages/Password.vue") 82 | }, 83 | { 84 | // pin page is for entering the signal registration pin. this is currently broken 85 | // and handled by the verification page 86 | path: "/pin", 87 | name: "pin", 88 | meta: { 89 | layout: Legacy, 90 | }, 91 | component: () => import("@/pages/Verification.vue") 92 | }, 93 | { 94 | path: "/setPassword", 95 | name: "setPassword", 96 | meta: { 97 | layout: Legacy, 98 | }, 99 | component: () => import("@/pages/SetPassword.vue") 100 | }, 101 | { 102 | path: "/setUsername", 103 | name: "setUsername", 104 | meta: { 105 | layout: Legacy, 106 | }, 107 | component: () => import("@/pages/SetUsername.vue") 108 | }, 109 | { 110 | path: "/contacts", 111 | name: "contacts", 112 | meta: { 113 | layout: Legacy, 114 | }, 115 | component: () => import("@/pages/Contacts.vue") 116 | }, 117 | { 118 | path: "/settings", 119 | name: "settings", 120 | meta: { 121 | layout: Legacy, 122 | }, 123 | component: () => import("@/pages/Settings.vue") 124 | }, 125 | { 126 | path: "/about", 127 | name: "about", 128 | meta: { 129 | layout: Legacy, 130 | }, 131 | component: () => import("@/pages/About.vue") 132 | }, 133 | { 134 | path: "/devices", 135 | name: "devices", 136 | meta: { 137 | layout: Legacy, 138 | }, 139 | component: () => import("@/pages/DeviceList.vue") 140 | }, 141 | { 142 | path: "/newGroup", 143 | name: "newGroup", 144 | meta: { 145 | layout: Legacy, 146 | }, 147 | component: () => import("@/pages/NewGroup.vue") 148 | }, 149 | { 150 | path: "/editGroup/:id", 151 | name: "editGroup", 152 | meta: { 153 | layout: Legacy, 154 | }, 155 | component: () => import("@/pages/EditGroup.vue") 156 | }, 157 | { 158 | path: "/qr", 159 | name: "qr", 160 | meta: { 161 | layout: Default, 162 | hasMenu: false, 163 | hasBackButton: true, 164 | }, 165 | component: () => import("@/pages/DeviceLinking.vue") 166 | }, 167 | { 168 | path: "/onboarding", 169 | name: "onboarding", 170 | meta: { 171 | layout: Default, 172 | hasMenu: false, 173 | hasBackButton: true, 174 | }, 175 | component: () => import("@/pages/OnBoarding.vue") 176 | }, 177 | { 178 | path: "/debug", 179 | name: "debug", 180 | meta: { 181 | layout: Legacy, 182 | }, 183 | component: () => import("@/pages/Debug.vue") 184 | }, 185 | { 186 | path: '/a', redirect: () => { 187 | return "/ws" 188 | // the function receives the target route as the argument 189 | // return redirect path/location here. 190 | } 191 | } 192 | ] 193 | }); 194 | 195 | router.beforeEach((to, from, next) => { 196 | if (to.query.token) { 197 | store.dispatch("setCaptchaToken", 198 | to.query.token) 199 | } 200 | if (to.path === "/debug") { 201 | return next(); 202 | } 203 | 204 | if (store.state.registrationStatus === null) { 205 | store.dispatch("getRegistrationStatus"); 206 | store.watch((state) => state.registrationStatus, function () { 207 | proceed(to, next); 208 | }); 209 | } else { 210 | proceed(to, next); 211 | } 212 | }); 213 | 214 | function proceed(to, next) { 215 | const registrationPages = ['/register', '/verify', '/password', '/pin', '/setUsername', '/qr', '/onboarding']; 216 | const registrationStatus = store.state.registrationStatus; 217 | //disable routes when registration is not finished yet 218 | if (registrationStatus === null && !registrationPages.includes(to.path)) { 219 | return next('/onboarding'); 220 | } else if (registrationStatus === "not_registered") { 221 | if (to.path === '/qr' || to.path === '/register' || to.path === '/onboarding') { 222 | let loader = document.getElementById('initial-loader'); 223 | if (typeof loader !== "undefined" && loader !== null) { 224 | loader.remove(); 225 | } 226 | return next(); 227 | } 228 | let loader = document.getElementById('initial-loader'); 229 | 230 | if (typeof loader !== "undefined" && loader !== null) { 231 | loader.remove(); 232 | } 233 | return next('/onboarding'); 234 | } 235 | else if ((registrationStatus === null || registrationStatus === "phoneNumber") && to.path !== '/register') { 236 | return next('/register'); 237 | } else if (registrationStatus === "verificationCode" && to.path !== '/verify') { 238 | return next('/verify'); 239 | } else if (registrationStatus === "pin" && to.path !== '/pin') { 240 | return next('/pin'); 241 | } else if (registrationStatus === "password" && to.path !== '/password') { 242 | return next('/password'); 243 | } else if (registrationStatus === "getUsername" && to.path !== '/setUsername') { 244 | return next('/setUsername'); 245 | } else if (registrationStatus === "registered" && registrationPages.includes(to.path)) { 246 | // We are registered. And are trying to access a registration page, redirect to home 247 | return next('/'); 248 | } else { 249 | next(); 250 | // The screen can be displayed ;) 251 | let loader = document.getElementById('initial-loader'); 252 | if (typeof loader !== "undefined" && loader !== null) { 253 | loader.remove(); 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /axolotl-web/tests/unit/components/Message.spec.js: -------------------------------------------------------------------------------- 1 | import { config, mount } from "@vue/test-utils"; 2 | import Message from "@/components/Message.vue"; 3 | import { expect } from "chai"; 4 | import linkifyHTML from "linkify-html"; 5 | import { createStore } from "vuex"; 6 | 7 | config.global = { 8 | directives: { 9 | Translate() { 10 | // do nothing in this test 11 | }, 12 | }, 13 | mixins: [ 14 | { 15 | methods: { 16 | linkify(content) { 17 | return linkifyHTML(content); 18 | }, 19 | }, 20 | }, 21 | ], 22 | stubs: ["FontAwesomeIcon"], 23 | }; 24 | 25 | describe("Message.vue", () => { 26 | const mockStore = createStore({ 27 | state: { 28 | config: {}, 29 | currentGroup: { 30 | Members: [], 31 | }, 32 | }, 33 | }); 34 | test("renders simple message without changes", () => { 35 | expect(Message).not.be.undefined; 36 | const msg = { 37 | message_type: "SyncMessage", 38 | sender: "a000000-5ddf-4fba-a6ee-2b0cb4663a6e", 39 | message:"🦎🍉🍓", 40 | timestamp: 1686505391763, 41 | is_outgoing: true, 42 | thread_id: null, 43 | attachments: [], 44 | is_sent: true, 45 | }; 46 | 47 | const wrapper = mount(Message, { 48 | props: { 49 | message: msg, 50 | isGroup: false, 51 | }, 52 | global: { 53 | plugins: [mockStore], // 54 | }, 55 | }); 56 | // Todo: check if message is rendered 57 | expect(wrapper.get('[data-test="message-text-content"]').wrapperElement.innerHTML).to.equal(msg.message) 58 | }); 59 | 60 | test("renders message with link linkified", () => { 61 | const expected = 'Visit axolotl.chat if you have time'; 62 | const msg = { 63 | message_type: "SyncMessage", 64 | sender: "a000000-5ddf-4fba-a6ee-2b0cb4663a6e", 65 | message: "Visit axolotl.chat if you have time", 66 | timestamp: 1686505391763, 67 | is_outgoing: true, 68 | thread_id: null, 69 | attachments: [], 70 | is_sent: true, 71 | }; 72 | const wrapper = mount(Message, { 73 | props: { 74 | message: msg, 75 | isGroup: false, 76 | }, 77 | global: { 78 | plugins: [mockStore], // 79 | }, 80 | }); 81 | // Todo: check if message is rendered 82 | 83 | expect(wrapper.get('[data-test="message-text-content"]').wrapperElement.innerHTML).to.equal(expected) 84 | }); 85 | 86 | test("renders message with html entities escaped", () => { 87 | const expected = "I <3 Axolotl"; 88 | const msg = { 89 | message_type: "SyncMessage", 90 | sender: "a000000-5ddf-4fba-a6ee-2b0cb4663a6e", 91 | message: "I <3 Axolotl", 92 | timestamp: 1686505391763, 93 | is_outgoing: true, 94 | thread_id: null, 95 | attachments: [], 96 | is_sent: true, 97 | }; 98 | const wrapper = mount(Message, { 99 | props: { 100 | message: msg, 101 | isGroup: false, 102 | }, 103 | global: { 104 | plugins: [mockStore], // 105 | }, 106 | }); 107 | 108 | expect(wrapper.get('[data-test="message-text-content"]').wrapperElement.textContent).to.equal(expected) 109 | }); 110 | 111 | test("does not interpred injected html code", () => { 112 | const msg = { 113 | message_type: "SyncMessage", 114 | sender: "a000000-5ddf-4fba-a6ee-2b0cb4663a6e", 115 | message: '
Injected Code
', 116 | timestamp: 1686505391763, 117 | is_outgoing: true, 118 | thread_id: null, 119 | attachments: [], 120 | is_sent: true, 121 | }; 122 | const wrapper = mount(Message, { 123 | props: { 124 | message: msg, 125 | isGroup: false, 126 | }, 127 | global: { 128 | plugins: [mockStore], // 129 | }, 130 | }); 131 | expect(wrapper.find('[data-test="html-injection"]').exists()).to.be.false; 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /axolotl-web/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import path from "path"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [vue()], 10 | resolve: { 11 | extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], 12 | alias: { 13 | "@": path.resolve(__dirname, "./src"), 14 | }, 15 | }, 16 | preview: { 17 | port: 8080, 18 | }, 19 | optimizeDeps: { 20 | include: ["mic-recorder-to-mp3"], 21 | }, 22 | build: { 23 | minify: false, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /axolotl-web/vitest.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import Vue from "@vitejs/plugin-vue"; 3 | import path from "path"; 4 | 5 | export default defineConfig({ 6 | plugins: [Vue()], 7 | resolve: { 8 | extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | test: { 14 | globals: true, 15 | environment: "jsdom", 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // #[cfg(feature = "tauri")] 3 | // tauri_build::build() 4 | } 5 | -------------------------------------------------------------------------------- /click/axolotl-helper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | f1, f2 = sys.argv[1:3] 4 | open(f2, "w").write(open(f1).read()) 5 | -------------------------------------------------------------------------------- /click/axolotl-push-helper.json: -------------------------------------------------------------------------------- 1 | { 2 | "exec": "axolotl-helper" 3 | } 4 | -------------------------------------------------------------------------------- /click/axolotl-push.apparmor: -------------------------------------------------------------------------------- 1 | { 2 | "template": "ubuntu-push-helper", 3 | "policy_groups": [ 4 | "push-notification-client" 5 | ], 6 | "policy_version": 20.04 7 | } 8 | -------------------------------------------------------------------------------- /click/axolotl.apparmor: -------------------------------------------------------------------------------- 1 | { 2 | "policy_groups": [ 3 | "audio", 4 | "camera", 5 | "connectivity", 6 | "content_exchange", 7 | "content_exchange_source", 8 | "networking", 9 | "video", 10 | "push-notification-client", 11 | "webview", 12 | "microphone" 13 | ], 14 | "policy_version": 20.04 15 | } 16 | -------------------------------------------------------------------------------- /click/axolotl.content-hub: -------------------------------------------------------------------------------- 1 | { 2 | "destination": [ 3 | "pictures", 4 | "videos", 5 | "links", 6 | "text", 7 | "documents" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /click/axolotl.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Axolotl 3 | Exec=env RUST_LOG=debug RUST_BACKTRACE=1 axolotl --mode ubuntu-touch %u 4 | Icon=axolotl.png 5 | Terminal=false 6 | Type=Application 7 | X-Lomiri-Touch=true 8 | X-Lomiri-Default-Department-ID=communication 9 | X-Lomiri-Splash-Color=#2090ea 10 | X-KDE-FormFactor=desktop;tablet;handset; 11 | X-Purism-FormFactor=Workstation;Mobile; 12 | -------------------------------------------------------------------------------- /click/axolotl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/click/axolotl.png -------------------------------------------------------------------------------- /click/axolotl.url-dispatcher: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "protocol": "sgnl" 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /click/notification.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Notification" 3 | 4 | # for sending 5 | gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/axolotl_2Enanuc --method com.ubuntu.Postal.Post axolotl.nanuc_axolotl '"{\"message\": \"foobar\", \"notification\":{\"card\": {\"summary\": \"Sofla\", \"body\": \"hello\", \"popup\": true, \"persist\": true}, \"tag\":\"chat\",\"sound\":\"buzz.mp3\", \"vibrate\":{\"pattern\":[200,100],\"duration\":200,\"repeat\":2}}}"' 6 | # show my persitent messages with the respective tag 7 | gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/axolotl_2Enanuc --method com.ubuntu.Postal.ListPersistent axolotl.nanuc_axolotl 8 | # clear all messages with tag chat 9 | gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/axolotl_2Enanuc --method com.ubuntu.Postal.ClearPersistent axolotl.nanuc_axolotl chat 10 | # get all my messages 11 | gdbus call --session --dest com.ubuntu.Postal --object-path /com/ubuntu/Postal/axolotl_2Enanuc --method com.ubuntu.Postal.PopAll axolotl.nanuc_axolotl 12 | -------------------------------------------------------------------------------- /clickable.yaml: -------------------------------------------------------------------------------- 1 | clickable_minimum_required: 8.2.0 2 | 3 | builder: rust 4 | rust_channel: stable 5 | build_args: --features ut 6 | 7 | dependencies_host: 8 | - gettext 9 | - protobuf-compiler 10 | dependencies_target: 11 | - libdbus-1-dev 12 | 13 | kill: axolotl 14 | framework: ubuntu-sdk-20.04 15 | 16 | install_root_data: 17 | - manifest.json 18 | - click/axolotl.png 19 | - click/axolotl-helper 20 | - click/axolotl-push-helper.json 21 | - click/axolotl-push.apparmor 22 | - click/axolotl.apparmor 23 | - click/axolotl.content-hub 24 | - click/axolotl.desktop 25 | - click/axolotl.png 26 | - click/axolotl.url-dispatcher 27 | - ${AXOLOTLWEB_LIB_INSTALL_DIR}/../../../all/axolotlweb/install/axolotl-web 28 | - guis/qml/ut 29 | 30 | libraries: 31 | axolotlweb: 32 | image_setup: 33 | run: 34 | # Install instructions taken from https://github.com/nodesource/distributions#installation-instructions 35 | - mkdir -p /etc/apt/keyrings 36 | - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg 37 | - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list 38 | - apt-get update && apt-get install -y nodejs yarn 39 | - echo "NODE Version:" && node --version 40 | - echo "YARN Version:" && yarn --version 41 | 42 | builder: custom 43 | restrict_arch: all 44 | src_dir: axolotl-web 45 | build: 46 | - cd ${SRC_DIR} && yarn install --frozen-lockfile && yarn run build 47 | - mkdir -p ${INSTALL_DIR}/axolotl-web 48 | - mv ${SRC_DIR}/dist ${INSTALL_DIR}/axolotl-web/ 49 | -------------------------------------------------------------------------------- /data/axolotl.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Axolotl 3 | GenericName=Signal Chat Client 4 | Comment=A signal client 5 | Type=Application 6 | Exec=axolotl --mode tauri 7 | Terminal=false 8 | Categories=Network;InstantMessaging;Chat; 9 | Icon=@icon@ 10 | StartupNotify=true 11 | StartupWMClass=axolotl 12 | X-KDE-FormFactor=desktop;tablet;handset; 13 | X-Purism-FormFactor=Workstation;Mobile; 14 | X-Gnome-UsesNotifications=true 15 | -------------------------------------------------------------------------------- /data/axolotl.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @app-id@ 7 | Axolotl 8 | Axolotl is a crossplattform Signal client 9 | @app-id@.desktop 10 | GPL-3.0 11 | CC0-1.0 12 | nanu-c 13 | https://axolotl.chat/ 14 | https://github.com/nanu-c/axolotl/issues 15 | https://www.patreon.com/nanuc 16 | 17 | 18 |

19 | Axolotl is a complete Signal client, it allows you to create a Signal 20 | account and have discussions with your contacts. Unlike the desktop 21 | Signal client, Axolotl is completely autonomous and doesn't require 22 | you to have created an account with the official Signal application. 23 |

24 |
25 | 26 | 27 | Axolotl screenshot 28 | https://raw.githubusercontent.com/nanu-c/axolotl/main/screenshot.png 29 | 30 | 31 | 32 | 33 | https://github.com/nanu-c/axolotl/releases/tag/v2.0.4 34 | 35 | 36 | https://github.com/nanu-c/axolotl/releases/tag/v1.6.0 37 | 38 | 39 | https://github.com/nanu-c/axolotl/releases/tag/v1.5.1 40 | 41 | 42 | https://github.com/nanu-c/axolotl/releases/tag/v1.5.0 43 | 44 | 45 | https://github.com/nanu-c/axolotl/releases/tag/v1.4.0 46 | 47 | 48 | https://github.com/nanu-c/axolotl/releases/tag/v1.3.1 49 | 50 | 51 | https://github.com/nanu-c/axolotl/releases/tag/v1.3.0 52 | 53 | 54 | https://github.com/nanu-c/axolotl/releases/tag/v1.2.0 55 | 56 | 57 | https://github.com/nanu-c/axolotl/releases/tag/v1.1.0 58 | 59 | 60 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.9 61 | 62 | 63 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.8 64 | 65 | 66 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.7 67 | 68 | 69 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.6 70 | 71 | 72 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.5 73 | 74 | 75 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.4 76 | 77 | 78 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.2 79 | 80 | 81 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.1.1 82 | 83 | 84 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.1 85 | 86 | 87 | https://github.com/nanu-c/axolotl/releases/tag/v1.0.0 88 | 89 | 90 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.9 91 | 92 | 93 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.7 94 | 95 | 96 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.7 97 | 98 | 99 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.6 100 | 101 | 102 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.5 103 | 104 | 105 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.4 106 | 107 | 108 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.3 109 | 110 | 111 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.2 112 | 113 | 114 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.0.1 115 | 116 | 117 | https://github.com/nanu-c/axolotl/releases/tag/v0.9.0 118 | 119 | 120 | https://github.com/nanu-c/axolotl/releases/tag/v0.8.9 121 | 122 | 123 | https://github.com/nanu-c/axolotl/releases/tag/v0.8.8 124 | 125 | 126 | https://github.com/nanu-c/axolotl/releases/tag/v0.8.7 127 | 128 | 129 | https://github.com/nanu-c/axolotl/releases/tag/v0.8.6 130 | 131 | 132 |
133 | -------------------------------------------------------------------------------- /data/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/data/icons/icon.icns -------------------------------------------------------------------------------- /data/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/data/icons/icon.ico -------------------------------------------------------------------------------- /data/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/data/icons/icon.png -------------------------------------------------------------------------------- /docs/DEBUG.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | This document is intended as a place to list some tips and tricks, mainly aimed for axolotl 4 | development. 5 | 6 | ## Browser access 7 | 8 | The Axolotl application can be accessed through the application window, but additionally 9 | it is possible to also access the application through any browser. 10 | 11 | To do so, just point a browser to `http://localhost:9080`. 12 | 13 | ## Run only frontend and connect to phone backend 14 | 15 | Per default, both the Axolotl frontend and backend is started on the same device. 16 | 17 | It is however also possible to connect the axolotl frontend to a backend running on another system, 18 | for example a phone. 19 | 20 | That way the Signal registration on your phone is used. 21 | 22 | - `cd axolotl-web` 23 | - `VITE_WS_ADDRESS=10.0.0.2 yarn run serve` (replace 10.0.0.2 with the IP of your phone) 24 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | Build the frontend first. See [instructions](./axolotl-web/README.md). 4 | 5 | ## Clickable 6 | 7 | ### Tooling 8 | 9 | This requires `clickable` to be installed locally (version 7 or above). 10 | Installation instructions can be found [here](https://clickable-ut.dev/en/dev/install.html). 11 | 12 | ### Build and Install 13 | 14 | **Note**: For the next three commands add `--arch ` (i.e. `--arch arm64`) to the command when building for a mobile device. 15 | 16 | 1. In order to build axolotl you need to get its nodejs dependencies once: 17 | 18 | `clickable build --libs axolotlweb` 19 | 20 | 2. Finally the app is built by running: 21 | 22 | `clickable` 23 | 24 | This will build the app, install it onto a device connected via usb and run the app on the device. 25 | 26 | All steps can be done with individual clickable commands `clickable build`, `clickable install` and `clickable launch`. To build and run Axolotl on your pc run `clickable desktop`. 27 | 28 | Clickable supports a few different parameters. Those can be set via command line or in the `clickable.yaml` file. For example run `clickable launch logs` to start signal and get logging output. 29 | 30 | For a full list of available clickable commands, see [here](https://clickable-ut.dev/en/latest/commands.html). 31 | 32 | ## Native build 33 | 34 | ### Rust 35 | 36 | Install Rust using [rustup](https://www.rust-lang.org/tools/install). 37 | 38 | ### Build Instructions 39 | 40 | Build axolotl 41 | 42 | ```bash 43 | make build 44 | ``` 45 | 46 | Building should work using both `stable` and `nightly` toolchains. 47 | 48 | 49 | ### Cross compile build 50 | 51 | #### cross 52 | 53 | To cross-compile for other targets, one approach is to use `cross` and specify the target flag. 54 | [Cross](https://github.com/rust-embedded/cross) provides an environment, cross toolchain and cross 55 | compiled libraries for building, without needing to install them separately. 56 | 57 | To install, use `cargo install cross`. 58 | 59 | To do a cross-compile build, use the following: 60 | 61 | ```bash 62 | cross build --release --target aarch64-unknown-linux-gnu 63 | cross build --release --target armv7-unknown-linux-gnueabihf 64 | ``` 65 | 66 | #### Natively 67 | 68 | Another approach of cross-compiling is to set up the dependencies yourself. 69 | 70 | For that two things are required. First install the required dependencies. 71 | For Ubuntu, the following packages are required. 72 | 73 | ```bash 74 | sudo apt install gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf 75 | ``` 76 | 77 | Then install the rust targets, e.g.: 78 | 79 | ```bash 80 | rustup target add aarch64-unknown-linux-gnu 81 | rustup target add armv7-unknown-linux-gnueabihf 82 | ``` 83 | 84 | Configure cargo with the cross-linker. For gcc: 85 | 86 | ```bash 87 | export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 88 | export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=armv7-unknown-linux-gnueabihf-gcc 89 | ``` 90 | 91 | To do a cross-compile build, use the following: 92 | 93 | ```bash 94 | cargo build --release --target aarch64-unknown-linux-gnu 95 | cargo build --release --target armv7-unknown-linux-gnueabihf 96 | ``` 97 | 98 | ## Snap 99 | 100 | ### Tooling 101 | 102 | This requires `snap` and `snapcraft` to be installed locally. 103 | Installation instructions for snapcraft can be found [here](https://snapcraft.io/docs/getting-started). 104 | 105 | ### Dependencies 106 | 107 | Snapcraft manages its own dependencies. 108 | 109 | ### Build and Install 110 | 111 | The Snap template used for the installation can be found 112 | in the /snap subdirectory. 113 | 114 | To build the application, use the following command from the root of this repository. 115 | 116 | `sudo snapcraft` 117 | 118 | To install the built snap, use snap: 119 | 120 | `sudo snap install axolotl_1.6.0_amd64.snap --dangerous` 121 | 122 | ### Run 123 | 124 | To start the application, either search for "Axolotl" in your app drawer or start it with the below command. 125 | 126 | `snap run axolotl` 127 | 128 | ## Flatpak 129 | 130 | ### Tooling 131 | 132 | This requires `flatpak` and `flatpak-builder` to be installed locally. 133 | Installation instructions can be found [here](https://flatpak.org/setup/) 134 | 135 | ### Dependencies 136 | 137 | The following Flatpak SDKs are required: 138 | ``` 139 | flatpak install install org.gnome.Platform//45 140 | flatpak install install org.gnome.Sdk//45 141 | flatpak install install org.freedesktop.Sdk.Extension.node18//22.08 142 | flatpak install install org.freedesktop.Sdk.Extension.rust-stable//22.08 143 | ``` 144 | 145 | ### Build and Install 146 | 147 | ``` 148 | cd flatpak 149 | flatpak-builder build org.nanuc.Axolotl.yml --force-clean --keep-build-dirs --ccache --user --install 150 | ``` 151 | 152 | ### Run 153 | 154 | To start the application, either search for "Axolotl" in your app drawer or start it with the below command. 155 | 156 | `flatpak run org.nanuc.Axolotl --mode tauri` 157 | 158 | ## AppImage 159 | 160 | ### Tooling 161 | 162 | This requires `yarn`, `cargo` and `tauri-cli` to be installed locally. 163 | 164 | ### Build 165 | 166 | ``` 167 | cargo tauri build --features tauri --bundles appimage 168 | ``` 169 | 170 | ### Run 171 | 172 | To start the application, execute the AppImage binary directly 173 | 174 | `target/release/bundle/appimage/*.AppImage` 175 | 176 | ## Debian 177 | 178 | ### Tooling 179 | 180 | This requires `yarn`, `cargo` and `tauri-cli` to be installed locally. 181 | 182 | ### Build and Install 183 | 184 | ``` 185 | cargo tauri build --features tauri --bundles deb 186 | sudo apt install ./target/release/bundle/deb/*.deb 187 | ``` 188 | 189 | ## Bare 190 | 191 | ### Tooling 192 | 193 | This requires `make` and `cargo` to be installed locally. 194 | 195 | ### Build and Install 196 | 197 | ``` 198 | make install 199 | ``` 200 | -------------------------------------------------------------------------------- /docs/NOTES.md: -------------------------------------------------------------------------------- 1 | #### Note: 2 | 3 | 4 | ### Message sending states 5 | 1. Click sending a message -> 6 | 2. Message isn't shown until backend processed it -> 7 | 3.1 Message sent to signal successfully (SendingError = false, isSent = false, isRead=false) 8 | 3.2 Messege sending failure because of missing connection for example (SendingError = true, isSent = false, isRead=false) 9 | 4. Message was received by other person (SendingError = false, isSent = true, isRead=false) 10 | 5. Message was read by other person (SendingError = false, isSent = true, isRead=true) 11 | -------------------------------------------------------------------------------- /docs/TRANSLATE.md: -------------------------------------------------------------------------------- 1 | # Translations 2 | 3 | Axolotl uses gettext for translations. For every language a `.po` file exists in the apps `/po/` subfolder. These `.po` files need to be translated. 4 | 5 | Instructions on how to translate using `.po` files are available here: http://docs.ubports.com/en/latest/contribute/translations.html#po-ts-file-editor 6 | 7 | Once you finished translating, test the strings. This should be done before commiting any changes on axolotl-web. 8 | 9 | The following dependencies are required and need to be installed: 10 | 11 | ``` 12 | sudo apt-get install gettext 13 | 14 | ``` 15 | 16 | Set up development environment as described under [README.md](README.md). 17 | 18 | Change into the `axolotl-web` subfolder and run: 19 | `yarn run translate` 20 | 21 | This command combines the following three single steps into one. Each of them can of course be run separately. 22 | 23 | - `yarn run translate-extract` extracting the language strings. This updates only the pot file. 24 | - `yarn run translate-update` for updating all the translation files. 25 | - `yarn run translate-compile` for updating the json file used by axolotl-web. Without that you don't see any results. 26 | 27 | Then open Axolotl and have a look at your strings by either installing your package (click, snap, flatpak or appImage) or as described in [README.md](README.md) under "Run development". 28 | -------------------------------------------------------------------------------- /docs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/docs/architecture.png -------------------------------------------------------------------------------- /examples/dump/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dump_db" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [source.crates-io] 7 | registry = "git://github.com/rust-lang/crates.io-index.git" 8 | 9 | [dependencies] 10 | 11 | sled = "0.34.7" 12 | 13 | -------------------------------------------------------------------------------- /examples/dump/dump_db.rs: -------------------------------------------------------------------------------- 1 | extern crate dirs; 2 | 3 | fn main() { 4 | println!("Axolotl dump_db v.0.1.0 Starting the dump"); 5 | let config_path = dirs::config_dir() 6 | .unwrap() 7 | .into_os_string() 8 | .into_string() 9 | .unwrap(); 10 | let db_path = format!("{config_path}/axolotl.nanuc"); 11 | 12 | let thedb = sled::open(db_path).unwrap(); 13 | 14 | dump_registration(thedb.clone()); 15 | dump_sessions(thedb.clone()); 16 | dump_groups(thedb.clone()); 17 | dump_contacts(thedb.clone()); 18 | println!("Done dumping the database"); 19 | } 20 | 21 | fn dump_registration(thedb: sled::Db) { 22 | // Iterate over all the items stored in sled to print them 23 | for kvr in thedb.iter() { 24 | if let Ok(kv) = kvr { 25 | let key = std::str::from_utf8(&kv.0); 26 | let value = std::str::from_utf8(&kv.1); 27 | println!("{:?} : {:?}\n", key, value); 28 | } else { 29 | println!("{:?}\n", kvr); 30 | } 31 | } 32 | } 33 | fn dump_sessions(thedb: sled::Db) { 34 | let sessions = thedb.open_tree("sessions").unwrap(); 35 | for kvr in sessions.iter() { 36 | if let Ok(kv) = kvr { 37 | let key = std::str::from_utf8(&kv.0); 38 | let value = std::str::from_utf8(&kv.1); 39 | println!("{:?} : {:?}\n", key, value); 40 | } else { 41 | println!("{:?}\n", kvr); 42 | } 43 | } 44 | } 45 | fn dump_groups(thedb: sled::Db) { 46 | // groups are in form of a protobuf and encrypted, so we can't dump them without loading the libsignal 47 | let groups = thedb.open_tree("groups").unwrap(); 48 | for kvr in groups.iter() { 49 | if let Ok(kv) = kvr { 50 | let key = std::str::from_utf8(&kv.0); 51 | let value = std::str::from_utf8(&kv.1); 52 | println!("{:?} : {:?}\n", key, value); 53 | } else { 54 | println!("{:?}\n", kvr); 55 | } 56 | } 57 | } 58 | 59 | fn dump_contacts(thedb: sled::Db) { 60 | let contacts = thedb.open_tree("contacts").unwrap(); 61 | for kvr in contacts.iter() { 62 | if let Ok(kv) = kvr { 63 | let key = std::str::from_utf8(&kv.0); 64 | let value = std::str::from_utf8(&kv.1); 65 | println!("{:?} : {:?}\n", key, value); 66 | } else { 67 | println!("{:?}\n", kvr); 68 | } 69 | } 70 | } 71 | 72 | // fn dump_messages(thedb: sled::Db) { 73 | // let contacts = thedb.open_tree("contacts").unwrap(); 74 | // for kvr in contacts.iter() { 75 | // if let Ok(kv) = kvr { 76 | // let uuid = Uuid::nil(); // Todo parse kv.1 as a contact and extract the uuid 77 | // let key = format!("threads:contact:{uuid}"); 78 | // let mut hasher = Sha256::new(); 79 | // hasher.update(key.as_bytes()); 80 | // } else { 81 | // println!("{:?}\n", kvr); 82 | // } 83 | // } 84 | // } 85 | -------------------------------------------------------------------------------- /flatpak/README.md: -------------------------------------------------------------------------------- 1 | # Flathub publishing 2 | 3 | Flathub is the largest, de facto standard location for Flatpak software. 4 | 5 | To publish an application there, a list of [App Requirements](https://github.com/flathub/flathub/wiki/App-Requirements) 6 | do all need to be fulfilled. 7 | 8 | One of these requirements is that of "Stable releases, reproducible builds". 9 | 10 | ## Dependencies 11 | 12 | To be published, all dependencies of the application needs to be listed in the Flatpak manifest. 13 | 14 | There is a set of [flatpak builder tools](https://github.com/flatpak/flatpak-builder-tools) provided as to assist with 15 | this dependency listing. 16 | 17 | ### axolotl-web 18 | 19 | Generate npm/yarn dependencies via [flatpak-node-generator](https://github.com/flatpak/flatpak-builder-tools/tree/master/node): 20 | 21 | ```sh 22 | sudo apt install pipx python3 23 | git clone git@github.com:flatpak/flatpak-builder-tools.git 24 | cd flatpak-builder-tools/node 25 | pipx install . 26 | flatpak-node-generator yarn ../../axolotl-web/yarn.lock -o ../../flatpak/node-sources.json 27 | ``` 28 | 29 | ### axolotl 30 | 31 | Generate cargo dependencies via [flatpak-cargo-generator](https://github.com/flatpak/flatpak-builder-tools/tree/master/cargo): 32 | 33 | ```sh 34 | sudo apt install python3 python3-aiohttp python3-toml python3-yaml 35 | git clone git@github.com:flatpak/flatpak-builder-tools.git 36 | cd flatpak-builder-tools/cargo 37 | python3 ./flatpak-cargo-generator.py ../../Cargo.lock -o ../../flatpak/cargo-sources.json 38 | ``` 39 | -------------------------------------------------------------------------------- /flatpak/org.nanuc.Axolotl.yml: -------------------------------------------------------------------------------- 1 | app-id: org.nanuc.Axolotl 2 | runtime: org.gnome.Platform 3 | runtime-version: "46" 4 | sdk: org.gnome.Sdk 5 | sdk-extensions: 6 | - org.freedesktop.Sdk.Extension.node20 7 | - org.freedesktop.Sdk.Extension.rust-stable 8 | command: axolotl 9 | finish-args: 10 | # See https://docs.flatpak.org/en/latest/sandbox-permissions-reference.html 11 | # Write access for the user download folder (to save media) 12 | - --filesystem=xdg-download:rw 13 | # TODO Can we use the Flatpak directories like XDG_CONFIG_DIR instead? 14 | # File access for configs (read/write access, and create the directory if it doesn’t exist) 15 | - --filesystem=~/.local/share/axolotl.nanuc:create 16 | - --filesystem=~/.config/axolotl.nanuc:create 17 | # Read access for home folder (to upload media) 18 | - --filesystem=home:ro 19 | # Network access - to receive and send messages 20 | - --share=network 21 | # X11 + XShm access 22 | - --share=ipc 23 | - --socket=fallback-x11 24 | # Wayland access 25 | - --socket=wayland 26 | # Sound access 27 | - --socket=pulseaudio 28 | # OpenGL access 29 | - --device=dri 30 | # To send and receive notifications 31 | - --talk-name=org.freedesktop.Notifications 32 | 33 | modules: 34 | - name: axolotl 35 | buildsystem: simple 36 | build-options: 37 | # Add the node bin directory. 38 | append-path: /usr/lib/sdk/node20/bin:/usr/lib/sdk/rust-stable/bin 39 | env: 40 | # Cargo config 41 | CARGO_HOME: /run/build/axolotl/cargo 42 | # Cargo offline mode 43 | CARGO_NET_OFFLINE: "true" 44 | build-commands: 45 | - make build-axolotl-web 46 | - make APP_ID=${FLATPAK_ID} DESTDIR=${FLATPAK_DEST} PREFIX="" install 47 | sources: 48 | - type: dir 49 | path: .. 50 | # Generated via flatpak-node-generator 51 | - node-sources.json 52 | # Generated via flatpak-cargo-generator 53 | - cargo-sources.json 54 | # Configure yarn to use the offline mirror 55 | - type: inline 56 | contents: | 57 | yarn-offline-mirror /run/build/axolotl/flatpak-node/yarn-mirror 58 | --install.offline true 59 | --run.offline true 60 | dest-filename: .yarnrc 61 | modules: 62 | - name: abseil 63 | buildsystem: cmake-ninja 64 | config-opts: 65 | - -DABSL_PROPAGATE_CXX_STD=ON 66 | - -DCMAKE_BUILD_TYPE=RelWithDebInfo 67 | cleanup: 68 | - /include 69 | - /lib/*.a 70 | - /lib/cmake 71 | - /lib/pkgconfig 72 | sources: 73 | - type: archive 74 | url: https://github.com/abseil/abseil-cpp/releases/download/20240116.2/abseil-cpp-20240116.2.tar.gz 75 | sha256: 733726b8c3a6d39a4120d7e45ea8b41a434cdacde401cba500f14236c49b39dc 76 | - name: protobuf 77 | buildsystem: cmake-ninja 78 | config-opts: 79 | - -Dprotobuf_ABSL_PROVIDER=package 80 | - -Dprotobuf_BUILD_TESTS=OFF 81 | - -DCMAKE_BUILD_TYPE=RelWithDebInfo 82 | cleanup: 83 | - protoc 84 | - /bin 85 | - /doc 86 | - /lib/*.a 87 | - /lib/*.la 88 | - /lib/libprotoc* 89 | - /lib/libprotobuf-lite* 90 | - /lib/plugins 91 | - /lib/pkgconfig 92 | sources: 93 | - type: archive 94 | url: https://github.com/protocolbuffers/protobuf/releases/download/v27.2/protobuf-27.2.tar.gz 95 | sha256: e4ff2aeb767da6f4f52485c2e72468960ddfe5262483879ef6ad552e52757a77 96 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axolotl.nanuc", 3 | "version": "2.0.4", 4 | "description": "A Signal compatible messaging client for Ubuntu phones", 5 | "title": "Axolotl", 6 | "architecture": "@CLICK_ARCH@", 7 | "framework" : "ubuntu-sdk-20.04", 8 | "hooks": { 9 | "axolotl": { 10 | "apparmor": "axolotl.apparmor", 11 | "desktop": "axolotl.desktop", 12 | "urls": "axolotl.url-dispatcher", 13 | "content-hub": "axolotl.content-hub" 14 | }, 15 | "push": { 16 | "apparmor": "axolotl-push.apparmor", 17 | "push-helper": "axolotl-push-helper.json" 18 | } 19 | }, 20 | "icon": "qml/phoneui/images/logo.png", 21 | "maintainer": "Aaron Kimmig " 22 | } 23 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | ar be bg cs da de el es fa fi fr hr hu in it iw ja kn-rIN ko mk nb nl no pl pt-BR pt ro ru sk sl sr sv ta tr vi zh-rCN -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | axolotl-web/src/pages/NewGroup.vue 2 | axolotl-web/src/pages/Contacts.vue 3 | axolotl-web/src/pages/ChatList.vue 4 | axolotl-web/src/pages/SetPassword.vue 5 | axolotl-web/src/pages/Register.vue 6 | axolotl-web/src/pages/Verification.vue 7 | axolotl-web/src/pages/About.vue 8 | axolotl-web/src/pages/EditGroup.vue 9 | axolotl-web/src/pages/MessageList.vue 10 | axolotl-web/src/pages/Password.vue 11 | axolotl-web/src/pages/Settings.vue 12 | axolotl-web/src/pages/DeviceList.vue 13 | axolotl-web/src/components/AddGroupMembersModal.vue 14 | axolotl-web/src/components/ImportUnavailableModal.vue 15 | axolotl-web/src/components/AttachmentBar.vue 16 | axolotl-web/src/components/ConfirmationModal.vue 17 | axolotl-web/src/components/AddContactModal.vue 18 | axolotl-web/src/components/Message.vue 19 | axolotl-web/src/components/EditContactModal.vue 20 | axolotl-web/src/components/IdentityModal.vue 21 | axolotl-web/src/components/Header.vue 22 | axolotl-web/src/components/StartChatModal.vue 23 | axolotl-web/src/components/ErrorModal.vue 24 | axolotl-web/src/components/AddDeviceModal.vue 25 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/screenshot.png -------------------------------------------------------------------------------- /snap/gui/axolotl.desktop: -------------------------------------------------------------------------------- 1 | # https://snapcraft.io/docs/desktop-menu-icon-support 2 | 3 | [Desktop Entry] 4 | Name=Axolotl 5 | Exec=axolotl 6 | Type=Application 7 | Comment=A signal client, snap version 8 | Terminal=false 9 | Categories=Network;InstantMessaging;Chat; 10 | Icon=${SNAP}/meta/gui/axolotl.png 11 | X-KDE-FormFactor=desktop;tablet;handset; 12 | X-Purism-FormFactor=Workstation;Mobile; 13 | -------------------------------------------------------------------------------- /snap/gui/axolotl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axolotl-chat/axolotl/5e17c9b545a93a839477cc6f55ca34f100577597/snap/gui/axolotl.png -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: axolotl 2 | summary: A signal client. 3 | description: | 4 | Axolotl is a cross-platform Signal client 5 | grade: stable 6 | confinement: strict 7 | base: core20 8 | icon: snap/gui/axolotl.png 9 | version: "1.6.0" 10 | architectures: 11 | - build-on: amd64 12 | - build-on: arm64 13 | - build-on: armhf 14 | parts: 15 | axolotl: 16 | # https://github.com/snapcore/snapcraft/blob/master/snapcraft/plugins/v1/go.py 17 | plugin: go 18 | source: . 19 | build-packages: 20 | - gcc 21 | - g++ 22 | stage-packages: 23 | # These are required for Electron 24 | - libasound2 25 | - libgconf-2-4 26 | - libnss3 27 | - libx11-xcb1 28 | - libxss1 29 | - libxtst6 30 | override-build: | 31 | snapcraftctl build 32 | mkdir --parents $SNAPCRAFT_PART_INSTALL/usr/lib 33 | 34 | axolotl-web: 35 | plugin: npm 36 | source: ./axolotl-web 37 | npm-node-version: 18.12.0 38 | build-environment: 39 | - npm_config_unsafe_perm: "true" 40 | - SUDO_UID: "0" 41 | - SUDO_GID: "0" 42 | - SUDO_USER: root 43 | organize: 44 | ../build/dist: "bin/axolotl-web" 45 | override-build: | 46 | mkdir -p /root/parts/axolotl-web/install 47 | if echo $(dpkg --print-architecture)|grep -q 'amd64'; then curl -s https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-x64.tar.gz | tar xzf - -C $SNAPCRAFT_PART_SRC/../install --strip-components=1 ; fi 48 | if echo $(dpkg --print-architecture)|grep -q 'arm64'; then curl -s https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-arm64.tar.gz | tar xzf - -C $SNAPCRAFT_PART_SRC/../install --strip-components=1 ; fi 49 | if echo $(dpkg --print-architecture)|grep -q 'armhf'; then curl -s https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-armv7l.tar.gz | tar xzf - -C $SNAPCRAFT_PART_SRC/../install --strip-components=1 ; fi 50 | export PATH=$SNAPCRAFT_PART_SRC/../install/bin/:$PATH 51 | npm config set unsafe-perm true 52 | npm install -g yarn 53 | yarn install --frozen-lockfile 54 | yarn run build 55 | cp -r dist ../install/bin/axolotl-web 56 | stage: 57 | - bin/axolotl-web 58 | 59 | plugs: 60 | browser-sandbox: 61 | allow-sandbox: false 62 | interface: browser-support 63 | 64 | apps: 65 | axolotl: 66 | command: bin/axolotl 67 | environment: 68 | TMPDIR: $XDG_RUNTIME_DIR 69 | XDG_DATA_HOME: $SNAP_USER_DATA 70 | # This is required for Electron 71 | extensions: [gnome-3-38] 72 | plugs: 73 | - desktop 74 | - desktop-legacy 75 | - wayland 76 | - unity7 77 | - opengl 78 | - network 79 | - network-bind 80 | - network-manager 81 | - pulseaudio 82 | - home 83 | - x11 84 | - gsettings 85 | - browser-sandbox 86 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use presage as p; 2 | use presage::prelude::ServiceError; 3 | use presage_store_sled::SledStoreError; 4 | 5 | const FAILED_TO_LOOK_UP_ADDRESS: &str = "failed to lookup address information"; 6 | type PresageError = presage::Error; 7 | 8 | #[derive(Debug)] 9 | pub enum ApplicationError { 10 | ManagerThreadPanic, 11 | NoInternet, 12 | Presage(presage::Error), 13 | SledStore(presage_store_sled::SledStoreError), 14 | UnauthorizedSignal, 15 | SendFailed(presage::libsignal_service::sender::MessageSenderError), 16 | ReceiveFailed(String), 17 | WebSocketError, 18 | WebSocketHandleMessageError(String), 19 | RegistrationError(String), 20 | InvalidRequest, 21 | RegistrationSuccesful, 22 | } 23 | 24 | impl std::fmt::Display for ApplicationError { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | ApplicationError::ManagerThreadPanic => writeln!( 28 | f, 29 | "A part of the application crashed." 30 | ), 31 | ApplicationError::NoInternet => writeln!( 32 | f, 33 | "There does not seem to be a connection to the internet available." 34 | ), 35 | 36 | ApplicationError::UnauthorizedSignal => writeln!( 37 | f, 38 | "You do not seem to be authorized with Signal. Please delete the database and relink the application." 39 | ), 40 | ApplicationError::SendFailed(_) => writeln!( 41 | f, 42 | "Sending a message failed." 43 | ), 44 | ApplicationError::ReceiveFailed(_) => writeln!( 45 | f, 46 | "Receiving a message failed." 47 | ), 48 | ApplicationError::Presage(e) => writeln!( 49 | f, 50 | "presage error: : {}", 51 | e 52 | ), 53 | ApplicationError::WebSocketError => writeln!( 54 | f, 55 | "The websocket connection to the signal server failed." 56 | ), 57 | ApplicationError::WebSocketHandleMessageError(e) => writeln!( 58 | f, 59 | "Couldn't handle websocket message.: {}", 60 | e 61 | ), 62 | ApplicationError::RegistrationError(e) => writeln!( 63 | f, 64 | "Registration failed.: {}", 65 | e 66 | ), 67 | ApplicationError::InvalidRequest=> writeln!( 68 | f, 69 | "Invalid request.", 70 | ), 71 | ApplicationError::SledStore(_) => writeln!( 72 | f, 73 | "Something unexpected happened with the database. Please retry later." 74 | ), 75 | ApplicationError::RegistrationSuccesful => writeln!( 76 | f, 77 | "Registration succesful." 78 | ), 79 | 80 | } 81 | } 82 | } 83 | 84 | // convert presage errors to application errors 85 | impl From for ApplicationError { 86 | fn from(e: PresageError) -> Self { 87 | log::info!("{:?}", e.to_string()); 88 | if e.to_string().contains(FAILED_TO_LOOK_UP_ADDRESS) { 89 | return ApplicationError::NoInternet; 90 | } 91 | match e { 92 | p::Error::ServiceError(ServiceError::Unauthorized) => { 93 | ApplicationError::UnauthorizedSignal 94 | } 95 | // p::Error::MessageSenderError(p::libsignal_service::sender::MessageSenderError { 96 | // recipient: _, 97 | // }) => ApplicationError::NoInternet, 98 | p::Error::MessageSenderError( 99 | p::libsignal_service::sender::MessageSenderError::ServiceError( 100 | p::libsignal_service::content::ServiceError::SendError { reason: e }, 101 | ), 102 | ) if e.to_string().contains(FAILED_TO_LOOK_UP_ADDRESS) => ApplicationError::NoInternet, 103 | p::Error::MessageSenderError(e) => ApplicationError::SendFailed(e), 104 | _ => ApplicationError::Presage(e), 105 | } 106 | } 107 | } 108 | // convert presage errors to application errors 109 | impl From for ApplicationError { 110 | fn from(e: SledStoreError) -> Self { 111 | ApplicationError::SledStore(e) 112 | } 113 | } 114 | // convert websocket errors to application errors 115 | impl From for ApplicationError { 116 | fn from(e: serde_json::Error) -> Self { 117 | ApplicationError::WebSocketHandleMessageError(e.to_string()) 118 | } 119 | } 120 | 121 | impl From for ApplicationError { 122 | fn from(e: warp::Error) -> Self { 123 | ApplicationError::WebSocketHandleMessageError(e.to_string()) 124 | } 125 | } 126 | 127 | impl ApplicationError { 128 | pub fn more_information(&self) -> String { 129 | match self { 130 | ApplicationError::NoInternet => "Please check your internet connection.".to_string(), 131 | ApplicationError::UnauthorizedSignal => { 132 | "Please delete the database and relink the device.".to_string() 133 | } 134 | ApplicationError::SendFailed(e) => format!("{:#?}", e), 135 | ApplicationError::ReceiveFailed(e) => format!("{:#?}", e), 136 | ApplicationError::Presage(e) => format!("{:#?}", e), 137 | ApplicationError::ManagerThreadPanic => { 138 | "Please restart the application with logging and report this issue.".to_string() 139 | } 140 | ApplicationError::WebSocketError => { 141 | "Please restart the application with logging and report this issue.".to_string() 142 | } 143 | ApplicationError::WebSocketHandleMessageError(e) => format!("{:#?}", e), 144 | ApplicationError::RegistrationError(e) => format!("{:#?}", e), 145 | ApplicationError::InvalidRequest => "Invalid request.".to_string(), 146 | ApplicationError::SledStore(e) => format!("{:#?}", e), 147 | ApplicationError::RegistrationSuccesful => "Registration succesful.".to_string(), 148 | } 149 | } 150 | 151 | pub fn should_report(&self) -> bool { 152 | match self { 153 | ApplicationError::NoInternet => false, 154 | ApplicationError::UnauthorizedSignal => false, 155 | ApplicationError::SendFailed(_) => false, 156 | ApplicationError::ReceiveFailed(_) => false, 157 | ApplicationError::Presage(_) => true, 158 | ApplicationError::ManagerThreadPanic => true, 159 | ApplicationError::WebSocketError => true, 160 | ApplicationError::WebSocketHandleMessageError(_) => true, 161 | ApplicationError::RegistrationError(_) => true, 162 | ApplicationError::InvalidRequest => false, 163 | ApplicationError::SledStore(_) => true, 164 | ApplicationError::RegistrationSuccesful => false, 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/handlers/registration.rs: -------------------------------------------------------------------------------- 1 | use presage::{manager::Confirmation, Manager}; 2 | use presage_store_sled::SledStore; 3 | 4 | pub enum State { 5 | Started, 6 | Confirming(Manager), 7 | Registered, 8 | } 9 | 10 | pub enum Registration { 11 | Unregistered, 12 | Chosen(State), 13 | } 14 | 15 | impl Registration { 16 | pub fn explain_for_log(&self) -> String { 17 | match self { 18 | Self::Unregistered => "No registration started yet.".to_string(), 19 | Self::Chosen(State::Started) => "Registration started.".to_string(), 20 | Self::Chosen(State::Confirming(_)) => { 21 | "Registration is waiting for confirmation.".to_string() 22 | } 23 | Self::Chosen(State::Registered) => "Registered.".to_string(), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod handlers; 3 | pub mod manager_thread; 4 | pub mod messages; 5 | pub mod requests; 6 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use axolotl::handlers::{create_and_run_backend, get_app_dir}; 2 | 3 | use tokio::{sync::mpsc, task::JoinHandle}; 4 | use warp::{ws::WebSocket, Filter, Rejection, Reply}; 5 | 6 | use clap::Parser; 7 | 8 | #[derive(clap::ValueEnum, Clone, PartialEq)] 9 | enum Mode { 10 | Daemon, 11 | #[cfg(feature = "ut")] 12 | UbuntuTouch, 13 | #[cfg(feature = "tauri")] 14 | Tauri, 15 | } 16 | 17 | #[derive(Parser)] 18 | #[clap(about = "a basic signal CLI to try things out")] 19 | struct Args { 20 | #[clap(long = "mode", short = 'm')] 21 | mode: Mode, 22 | } 23 | 24 | #[tokio::main] 25 | async fn main() { 26 | env_logger::Builder::from_default_env() 27 | .parse_env("debug") 28 | .init(); 29 | let args = Args::parse(); 30 | 31 | let ui_handle = start_ui(args.mode).await; 32 | run_backend().await; 33 | match ui_handle.await { 34 | Ok(_) => {} 35 | Err(e) => { 36 | log::error!("Error while running the UI: {:?}", e); 37 | } 38 | }; 39 | } 40 | 41 | async fn run_backend() { 42 | let (request_tx, request_rx) = mpsc::channel(1); 43 | let server_task = tokio::spawn(async { 44 | run_websocket(request_tx).await; 45 | }); 46 | 47 | match create_and_run_backend(request_rx).await { 48 | Ok(_) => {} 49 | Err(e) => { 50 | log::error!("Error while running the backend: {:?}", e); 51 | } 52 | }; 53 | match server_task.await { 54 | Ok(_) => {} 55 | Err(e) => { 56 | log::error!("Error while running the server: {:?}", e); 57 | } 58 | }; 59 | } 60 | 61 | async fn run_websocket(handler: mpsc::Sender) { 62 | log::info!("Starting the websocket server"); 63 | 64 | let axolotl_ws_route = warp::path("ws") 65 | .and(warp::ws()) 66 | .and(warp::any().map(move || handler.clone())) 67 | .and_then(handle_ws_client); 68 | 69 | // Just serve the attachments/ directory 70 | let axolotl_http_attachments_route = warp::path("attachments").and(warp::fs::dir(format!( 71 | "{}/{}", 72 | get_app_dir(), 73 | "attachments" 74 | ))); 75 | 76 | warp::serve(axolotl_ws_route.or(axolotl_http_attachments_route)) 77 | .run(([127, 0, 0, 1], 9080)) 78 | .await; 79 | log::info!("Server stopped"); 80 | } 81 | 82 | pub async fn handle_ws_client( 83 | websocket: warp::ws::Ws, 84 | handler: mpsc::Sender, 85 | ) -> Result { 86 | Ok(websocket.on_upgrade(move |websocket| async move { 87 | log::debug!("New websocket connection"); 88 | let _ = handler.send(websocket).await; 89 | })) 90 | } 91 | 92 | async fn start_ui(mode: Mode) -> JoinHandle<()> { 93 | tokio::spawn(async move { 94 | match mode { 95 | #[cfg(feature = "tauri")] 96 | Mode::Tauri => { 97 | log::info!("Starting the tauri client"); 98 | tauri::start_tauri().await; 99 | } 100 | #[cfg(feature = "ut")] 101 | Mode::UbuntuTouch => { 102 | log::info!("Starting the Ubuntu Touch client"); 103 | ut::start_ut().await; 104 | } 105 | Mode::Daemon => { 106 | log::info!("Running headless"); 107 | } 108 | } 109 | }) 110 | } 111 | 112 | #[cfg(feature = "tauri")] 113 | mod tauri { 114 | const INIT_SCRIPT: &str = r#" 115 | document.addEventListener('DOMContentLoaded', function () { 116 | console.log("DOMContentLoaded"); 117 | window.renderCallback = function (scheme, sitekey, action, token) { 118 | 119 | var targetURL = "tauri://localhost/?token=" + [scheme, sitekey, action, token].join("."); 120 | var link = document.createElement("a"); 121 | link.href = targetURL; 122 | link.innerText = "open axolotl"; 123 | 124 | document.body.removeAttribute("class"); 125 | setTimeout(function () { 126 | document.getElementById("container").appendChild(link); 127 | }, 2000); 128 | 129 | window.location.href = targetURL; 130 | }; 131 | window.intercept = function() { 132 | console.log("intercept") 133 | console.log("resetting captcha") 134 | document.getElementById("captcha").innerHTML = ""; 135 | if(useHcaptcha)onloadHcaptcha(); 136 | else onload(); 137 | } 138 | if (!window.location.href.includes("localhost")){ 139 | intercept(); 140 | } else { 141 | console.log("localhost detected, not intercepting"); 142 | } 143 | }); 144 | "#; 145 | 146 | pub async fn start_tauri() { 147 | let t = tauri::Builder::default().any_thread().setup(|app| { 148 | tauri::WebviewWindowBuilder::new( 149 | app, 150 | "label", 151 | tauri::WebviewUrl::App("index.html".into()), 152 | ) 153 | .initialization_script(INIT_SCRIPT) 154 | .title("Axolotl") 155 | .build() 156 | .unwrap(); 157 | Ok(()) 158 | }); 159 | t.run(tauri::generate_context!()) 160 | .expect("error while running tauri application"); 161 | } 162 | } 163 | 164 | #[cfg(feature = "ut")] 165 | mod ut { 166 | use super::*; 167 | use std::process::exit; 168 | use std::process::Command; 169 | use std::process::Stdio; 170 | 171 | pub async fn start_ut() { 172 | log::info!("Starting the ubuntu touch client"); 173 | let _warp = tokio::spawn(async { 174 | let route = warp::fs::dir("./axolotl-web/dist"); 175 | warp::serve(route).run(([127, 0, 0, 1], 9081)).await; 176 | }); 177 | tokio::task::spawn_blocking(|| { 178 | Command::new("qmlscene") 179 | .arg("--scaling") 180 | .arg("--webEngineArgs") 181 | .arg("--remote-debugging-port") 182 | .arg("ut/MainUt.qml") 183 | .stdout(Stdio::piped()) 184 | .spawn() 185 | .expect("GUI failed to start") 186 | .wait() 187 | .unwrap() 188 | }) 189 | .await 190 | .unwrap(); 191 | 192 | exit(0); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | //! The messages module 2 | 3 | use crate::error::ApplicationError; 4 | use crate::manager_thread::ManagerThread; 5 | use crate::requests::{AxolotlMessage, AxolotlResponse, SendMessageResponse}; 6 | use presage::prelude::*; 7 | use presage::proto::{DataMessage, GroupContextV2}; 8 | use presage::store::ContentsStore; 9 | use presage::{Manager, Thread}; 10 | use presage_store_sled::SledStore; 11 | use std::time::UNIX_EPOCH; 12 | 13 | /** 14 | * Send a message to one people or a group. 15 | * 16 | * - recipient is a String containing the UUID of the recipient. A contact or a 17 | * group, both are supported. 18 | * - msg is an optional String containing the text body of the message. Most messages 19 | * would have it. 20 | * - attachments is an optional Vec of AttachmentPointer. The attachments must be 21 | * already uploaded, here they are only sent. 22 | * - manager is the instance of ManagerThread. 23 | * - response_type is a string slice containing the Axolotl response type. This 24 | * parameter is mandatory because the method is used to send message but also to 25 | * send attachments. Could be removed in the future if both handlers are merged. 26 | */ 27 | pub async fn send_message( 28 | recipient: Thread, 29 | msg: Option, 30 | attachments: Option>, 31 | manager: &ManagerThread, 32 | response_type: &str, 33 | ) -> Result { 34 | log::info!("Sending a message."); 35 | let timestamp = std::time::SystemTime::now() 36 | .duration_since(UNIX_EPOCH) 37 | .expect("Time went backwards") 38 | .as_millis() as u64; 39 | 40 | // Add the attachments to the message, if any 41 | let mut attachments_vec = Vec::new(); 42 | if let Some(a) = attachments { 43 | attachments_vec = a; 44 | } 45 | 46 | log::debug!("Sending {} attachments", attachments_vec.len()); 47 | let data_message = DataMessage { 48 | body: msg, 49 | timestamp: Some(timestamp), 50 | attachments: attachments_vec, 51 | ..Default::default() 52 | }; 53 | 54 | // Search the recipient's UUID. A contact or a group 55 | let result = match recipient { 56 | Thread::Contact(uuid) => { 57 | log::debug!("Sending a message to a contact."); 58 | manager 59 | .send_message(uuid, data_message.clone(), timestamp) 60 | .await 61 | } 62 | Thread::Group(uuid) => { 63 | log::debug!("Sending a message to a group."); 64 | manager 65 | .send_message_to_group(uuid, data_message.clone(), timestamp) 66 | .await 67 | } 68 | }; 69 | let is_failed = result.is_err(); 70 | if is_failed { 71 | log::error!( 72 | "send_message: Error while sending the message. {:?}", 73 | result.err() 74 | ); 75 | } 76 | let mut message = AxolotlMessage::from_data_message(data_message); 77 | message.thread_id = Some(recipient); 78 | // message.sender = Some(manager.uuid()); 79 | let response_data = SendMessageResponse { message, is_failed }; 80 | let response_data_json = serde_json::to_string(&response_data)?; 81 | let response = AxolotlResponse { 82 | response_type: response_type.to_string(), 83 | data: response_data_json, 84 | }; 85 | Ok(response) 86 | } 87 | 88 | pub async fn send_message_to_group( 89 | msg: &str, 90 | master_key_str: &str, 91 | attachments: Option>, 92 | config_store: SledStore, 93 | ) { 94 | let mut manager = match Manager::load_registered(config_store).await { 95 | Ok(m) => m, 96 | Err(e) => { 97 | println!("Error while loading the manager: {:?}", e); 98 | return; 99 | } 100 | }; 101 | // Send message 102 | let timestamp = std::time::SystemTime::now() 103 | .duration_since(UNIX_EPOCH) 104 | .expect("Time went backwards") 105 | .as_millis() as u64; 106 | 107 | // Add the attachments to the message, if any 108 | let mut attachments_vec = Vec::new(); 109 | if let Some(a) = attachments { 110 | attachments_vec = a; 111 | } 112 | 113 | let master_key: [u8; 32] = hex::decode(master_key_str).unwrap().try_into().unwrap(); 114 | 115 | let message = DataMessage { 116 | body: Some(msg.to_string()), 117 | timestamp: Some(timestamp), 118 | attachments: attachments_vec, 119 | group_v2: Some(GroupContextV2 { 120 | master_key: Some(master_key.to_vec()), 121 | revision: Some(0), 122 | ..Default::default() 123 | }), 124 | ..Default::default() 125 | }; 126 | 127 | match manager.store().group(master_key) { 128 | Ok(group) => match group { 129 | Some(_) => { 130 | manager 131 | .send_message_to_group(&master_key, message, timestamp) 132 | .await 133 | .unwrap(); 134 | } 135 | None => { 136 | println!("Group not found"); 137 | } 138 | }, 139 | Err(e) => { 140 | println!("Group not found: {:?}", e); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "security": { 4 | "csp": null 5 | }, 6 | "windows": [] 7 | }, 8 | "build": { 9 | "beforeBuildCommand": "yarn --cwd axolotl-web run build", 10 | "beforeDevCommand": "yarn --cwd axolotl-web run dev", 11 | "devUrl": "http://localhost:5173/", 12 | "frontendDist": "./axolotl-web/dist" 13 | }, 14 | "bundle": { 15 | "active": true, 16 | "category": "SocialNetworking", 17 | "copyright": "", 18 | "externalBin": [], 19 | "icon": [ 20 | "data/icons/icon.png", 21 | "data/icons/icon.icns", 22 | "data/icons/icon.ico" 23 | ], 24 | "licenseFile": "LICENSE", 25 | "linux": { 26 | "appimage": { 27 | "bundleMediaFramework": true 28 | }, 29 | "deb": { 30 | "depends": [ 31 | "libwebkit2gtk-4.1-0" 32 | ], 33 | "files": { 34 | "/usr/share/doc/axolotl/README.md": "README.md" 35 | } 36 | } 37 | }, 38 | "longDescription": "", 39 | "macOS": { 40 | "entitlements": null, 41 | "exceptionDomain": "", 42 | "frameworks": [], 43 | "providerShortName": null, 44 | "signingIdentity": null 45 | }, 46 | "publisher": "Aaron Kimmig", 47 | "resources": [], 48 | "shortDescription": "", 49 | "targets": [ 50 | "deb", 51 | "appimage" 52 | ], 53 | "windows": { 54 | "certificateThumbprint": null, 55 | "digestAlgorithm": "sha256", 56 | "timestampUrl": "" 57 | } 58 | }, 59 | "identifier": "nanu-c.axolotl", 60 | "productName": "Axolotl", 61 | "version": "2.0.1" 62 | } 63 | -------------------------------------------------------------------------------- /vetur.config.js: -------------------------------------------------------------------------------- 1 | // vetur.config.js 2 | /** @type {import('vls').VeturConfig} */ 3 | module.exports = { 4 | // **optional** default: `{}` 5 | // override vscode settings 6 | // Notice: It only affects the settings used by Vetur. 7 | settings: { 8 | "vetur.useWorkspaceDependencies": true, 9 | "vetur.experimental.templateInterpolationService": true 10 | }, 11 | // **optional** default: `[{ root: './' }]` 12 | // support monorepos 13 | projects: [ 14 | './axolotl-web/', 15 | ], 16 | } --------------------------------------------------------------------------------