├── .dockerignore ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── close-unreproduced.yml │ ├── docker-image-pr.yml │ ├── docker-image.yml │ ├── e2e-test.yml │ └── lint.yml ├── .gitignore ├── .npmrc ├── .nycrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── certs └── .gitkeep ├── cypress.config.js ├── cypress ├── e2e │ └── app.js └── support │ ├── e2e.js │ └── mock-socket.js ├── doap.xml ├── docs ├── ansible │ └── xmpp-web │ │ ├── README.md │ │ ├── defaults │ │ └── main.yml │ │ ├── handlers │ │ └── main.yml │ │ ├── meta │ │ └── main.yml │ │ ├── tasks │ │ ├── apache.yml │ │ └── main.yml │ │ └── templates │ │ ├── 024-chat-proxy.conf.j2 │ │ ├── 024-chat-ssl.conf.j2 │ │ └── 024-chat.conf.j2 ├── apache.conf ├── docker │ ├── 998-update-local.js.sh │ ├── Dockerfile │ ├── Dockerfile-dev │ ├── Dockerfile-multiarch │ └── default.conf.template ├── git-hooks │ └── commit-msg ├── screenshot-desktop-main.png ├── screenshot-guest-join.png ├── screenshot-mobile-chat.png ├── screenshot-mobile-main.png ├── staging-environments │ ├── README.MD │ ├── REPRODUCING.MD │ └── prosody-std │ │ ├── README.MD │ │ ├── data │ │ └── .gitignore │ │ ├── docker-compose.yml │ │ ├── entrypoint.sh │ │ ├── modules │ │ └── .gitignore │ │ └── prosody.cfg.lua └── update.sh ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg ├── local.js └── robots.txt ├── src ├── App.vue ├── assets │ ├── defaultAvatar.js │ └── styles.scss ├── components │ ├── About.vue │ ├── Avatar.vue │ ├── BookmarkButton.vue │ ├── Chat.vue │ ├── Contact.vue │ ├── Contacts.vue │ ├── EmojiPicker.vue │ ├── Group.vue │ ├── GuestChat.vue │ ├── GuestHome.vue │ ├── GuestRooms.vue │ ├── Home.vue │ ├── InviteGuestButton.vue │ ├── Login.vue │ ├── Message.vue │ ├── MessageLink.vue │ ├── Modal.vue │ ├── Navbar.vue │ ├── NotificationsSwitch.vue │ ├── Presence.vue │ ├── PresenceController.vue │ ├── Profile.vue │ ├── RetrieveHistoryButton.vue │ ├── RoomConfiguration.vue │ ├── RoomConfigurationButton.vue │ ├── RoomCreation.vue │ ├── RoomOccupants.vue │ ├── RoomSubject.vue │ ├── RoomsList.vue │ ├── Sendbox.vue │ └── Version.vue ├── main.js ├── router │ └── index.js ├── services │ ├── XmppClient.js │ └── XmppSocket.js └── store │ └── index.js └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .github 3 | .dockerignore 4 | node_modules 5 | dist 6 | docs/ansible 7 | docs/staging-environments 8 | docs/*.png 9 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'root': true, 3 | 'extends': [ 4 | 'plugin:vue/vue3-recommended', 5 | 'eslint:recommended', 6 | ], 7 | 'env': { 8 | node: true, 9 | es2021: true, 10 | 'vue/setup-compiler-macros': true, 11 | }, 12 | 'rules': { 13 | 'no-console': 'off', 14 | 'vue/singleline-html-element-content-newline': 'off', 15 | 'vue/max-attributes-per-line': 'off', 16 | 'vue/array-bracket-spacing': [ 17 | 'error', 18 | 'never', 19 | ], 20 | 'array-bracket-spacing': [ 21 | 'error', 22 | 'never', 23 | ], 24 | 'vue/arrow-spacing': 'error', 25 | 'arrow-spacing': 'error', 26 | 'semi': [ 27 | 'error', 28 | 'never', 29 | ], 30 | 'vue/block-spacing': 'error', 31 | 'block-spacing': 'error', 32 | 'vue/brace-style': 'error', 33 | 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 34 | 'vue/camelcase': 'error', 35 | 'camelcase': 'error', 36 | 'vue/comma-dangle': [ 37 | 'error', 38 | 'always-multiline', 39 | ], 40 | 'comma-dangle': [ 41 | 'error', 42 | 'always-multiline', 43 | ], 44 | 'vue/component-name-in-template-casing': [ 45 | 'error', 46 | 'kebab-case', 47 | ], 48 | 'vue/eqeqeq': 'error', 49 | 'eqeqeq': 'error', 50 | 'vue/key-spacing': 'error', 51 | 'key-spacing': 'error', 52 | 'vue/object-curly-spacing': ['error', 'always'], 53 | 'object-curly-spacing': ['error', 'always'], 54 | 'vue/space-unary-ops': 'error', 55 | 'space-unary-ops': 'error', 56 | 'vue/multi-word-component-names': 'off', 57 | 'indent': ['error', 2, { 'SwitchCase': 1 }], 58 | 'quotes': [2, 'single', 'avoid-escape'], 59 | 'no-trailing-spaces': 'error', 60 | 'space-infix-ops': 'error', 61 | 'no-multi-spaces': 'error', 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report to help us improve (for question, please ask it in discussions) 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | If it's just a question, **please do not create an issue but ask it in [discussions](https://github.com/nioc/xmpp-web/discussions/categories/q-a)**. 9 | 10 | Thanks for taking the time to fill out this bug report completely. 11 | The information you provide will help us to solve your problems faster and better. 12 | In the absence of this information, the report will be closed immediately. 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Describe the bug 17 | description: | 18 | A clear and concise description of what the bug is. 19 | You can paste screenshot if relevant. 20 | placeholder: "ex: Displayed Jid is incorrect in contacts list" 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: reproduce 25 | attributes: 26 | label: Steps to reproduce 27 | description: Steps to reproduce the problem. 28 | placeholder: | 29 | 1. Go to '...' 30 | 2. Click on '....' 31 | 3. Scroll down to '....' 32 | 4. See error 33 | validations: 34 | required: false 35 | - type: textarea 36 | id: behavior 37 | attributes: 38 | label: Expected behavior 39 | description: A clear and concise description of what **you expected to happen**. 40 | placeholder: "ex: Contacts list should display the nickname if present or Jid (name@domain.ltd)" 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: logs 45 | attributes: 46 | label: Relevant log 47 | description: | 48 | If applicable, please copy and paste any relevant console logs and/or websocket trames output. 49 | This will be automatically formatted into code, so no need for backticks. 50 | Pay attention to **remove/hide personal information** (server url, authentication, ...). 51 | render: Text 52 | validations: 53 | required: false 54 | - type: textarea 55 | id: localjs 56 | attributes: 57 | label: local.js configuration 58 | description: | 59 | Content of your `public/local.js` file. This will be automatically formatted into code, so no need for backticks. 60 | Pay attention to **remove/hide personal information** (server url, authentication, ...). 61 | render: js 62 | placeholder: | 63 | // eslint-disable-next-line no-unused-vars, no-var 64 | var config = { 65 | name: 'XMPP web', 66 | transports: { 67 | websocket: 'wss://chat.domain-web.ltd/xmpp-websocket', 68 | }, 69 | hasGuestAccess: true, 70 | hasRegisteredAccess: true, 71 | anonymousHost: 'anon.domain-xmpp.ltd', 72 | isTransportsUserAllowed: false, 73 | hasHttpAutoDiscovery: false, 74 | resource: 'Web XMPP', 75 | defaultDomain: 'domain-xmpp.ltd', 76 | defaultMuc: 'conference.domain-xmpp.ltd', 77 | isStylingDisabled: false, 78 | hasSendingEnterKey: false, 79 | connectTimeout: 5000, 80 | } 81 | validations: 82 | required: false 83 | - type: input 84 | id: version 85 | attributes: 86 | label: XMPP-web version 87 | description: | 88 | What version of this application are you running? 89 | You can find it in the `/about` page. 90 | placeholder: "ex: 0.9.6" 91 | validations: 92 | required: true 93 | - type: dropdown 94 | id: installation 95 | attributes: 96 | label: Installation 97 | description: How this application has been installed? 98 | options: 99 | - Docker image 100 | - Github release archive 101 | - Build from source 102 | - Ansible role 103 | - I do not know 104 | validations: 105 | required: true 106 | - type: dropdown 107 | id: xmpp 108 | attributes: 109 | label: XMPP server(s) 110 | description: What XMPP server are you using? 111 | multiple: true 112 | options: 113 | - Prosody IM 114 | - ejabberd 115 | - Openfire 116 | - Tigase XMPP Server 117 | - other 118 | - I do not know 119 | validations: 120 | required: true 121 | - type: dropdown 122 | id: browsers 123 | attributes: 124 | label: Browser(s) 125 | description: What browsers are you seeing the problem on? 126 | multiple: true 127 | options: 128 | - Firefox 129 | - Chrome 130 | - Microsoft Edge 131 | - Safari 132 | - other 133 | validations: 134 | required: false 135 | - type: dropdown 136 | id: devices 137 | attributes: 138 | label: Device(s) 139 | description: What devices are you using? 140 | multiple: true 141 | options: 142 | - desktop 143 | - mobile 144 | - other 145 | validations: 146 | required: false 147 | - type: textarea 148 | id: additional 149 | attributes: 150 | label: Other information 151 | description: Other diagnostic information that may be useful for troubleshooting. 152 | validations: 153 | required: false 154 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this feature request report. 9 | It’s up to you to make a solid case to convince us of the merits of this feature. 10 | - type: textarea 11 | id: behavior 12 | attributes: 13 | label: Intended behavior 14 | description: | 15 | Describe the intended behavior in a clear and concise description of what the feature should enhance in the application. 16 | If this is related to a [XMPP specification](https://xmpp.org/extensions), link the relevant page. 17 | placeholder: | 18 | ex: I'm always frustrated when [...] 19 | It should be more clear if [...] 20 | This is a more complete implementation of [XEP-0045](https://xmpp.org/extensions/xep-0045.html). 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: Solution 27 | description: | 28 | Describe the solution you'd like in a clear and concise description of what you want to happen. 29 | You can paste mock-ups if relevant. 30 | placeholder: | 31 | ex: App should listen the XMPP events which indicates [...] 32 | Then, information would be displayed in the component [...] 33 | validations: 34 | required: false 35 | - type: textarea 36 | id: alternatives 37 | attributes: 38 | label: Alternatives 39 | description: "Describe in a clear and concise description of any alternative solutions or features you've considered." 40 | placeholder: | 41 | Another option is to request server for getting data [...] 42 | validations: 43 | required: false 44 | - type: dropdown 45 | id: priority 46 | attributes: 47 | label: Priority 48 | description: From *your perspective*, what is the priority of this feature? 49 | multiple: false 50 | options: 51 | - Could-have 52 | - Should-have 53 | - Must-have 54 | validations: 55 | required: true 56 | - type: dropdown 57 | id: scope 58 | attributes: 59 | label: Users scope 60 | description: What kind(s) of user access would be affected? 61 | multiple: true 62 | options: 63 | - registered users 64 | - guests 65 | validations: 66 | required: false 67 | - type: dropdown 68 | id: features 69 | attributes: 70 | label: Affected features 71 | description: What application features/components would be affected? 72 | multiple: true 73 | options: 74 | - login 75 | - contacts list 76 | - rooms list 77 | - service discovery 78 | - room management 79 | - messages display 80 | - message edition / send box 81 | - presence 82 | validations: 83 | required: false 84 | - type: dropdown 85 | id: pr 86 | attributes: 87 | label: Pull request 88 | description: | 89 | Do you plan to handle this feature by submitting a pull request? 90 | Before writing code, please **wait that we confirm** this feature is relevant for the application. 91 | You should read the [contributing](../blob/master/CONTRIBUTING.md) guide. 92 | multiple: false 93 | options: 94 | - "yes" 95 | - "no" 96 | validations: 97 | required: true 98 | -------------------------------------------------------------------------------- /.github/workflows/close-unreproduced.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues that have not met the reproducibility requirements for a specified amount of time. 2 | # 3 | # For more information, see: 4 | # https://github.com/actions/stale 5 | name: Close unreproduced issues 6 | 7 | on: 8 | schedule: 9 | - cron: '43 */12 * * *' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v9 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | # stale delay: 25 | days-before-stale: 2 26 | days-before-pr-stale: -1 27 | # close delay: 28 | days-before-close: 13 29 | days-before-pr-close: -1 30 | # extra conditions 31 | only-labels: 'need repro' 32 | exempt-all-milestones: true 33 | # stale changes: 34 | stale-issue-label: 'stale' 35 | stale-issue-message: 'This issue will be closed within two weeks if the requested reproduction data is not provided. Please refer to this [guide](https://github.com/nioc/xmpp-web/tree/master/docs/staging-environments/REPRODUCING.MD ).' 36 | # close changes 37 | close-issue-reason: not_planned 38 | close-issue-label: 'wontfix' 39 | close-issue-message: 'This issue has been closed since it cannot be reproduced without the data you were asked for.' 40 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-pr.yml: -------------------------------------------------------------------------------- 1 | name: Publish a branch for review 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Read metadata 11 | uses: docker/metadata-action@v5 12 | id: meta 13 | with: 14 | images: nioc/xmpp-web 15 | flavor: | 16 | latest=false 17 | 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Setup Node.js 20 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20.x 27 | cache: 'npm' 28 | cache-dependency-path: 'package-lock.json' 29 | 30 | - name: Build front 31 | run: npm ci && npm run build 32 | 33 | - name: Create release archive 34 | shell: bash 35 | run: | 36 | mv dist xmpp-web && 37 | tar zcvf xmpp-web-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}.tar.gz xmpp-web 38 | 39 | - name: Draft Github release with archive 40 | uses: softprops/action-gh-release@v2 41 | with: 42 | draft: true 43 | prerelease: true 44 | files: xmpp-web-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}.tar.gz 45 | body: | 46 | ### :warning: Disclaimer 47 | 48 | This a draft prerelease for the ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} branch. 49 | 50 | - name: Prepare docker build context 51 | shell: bash 52 | run: | 53 | mkdir -p image-build/docs && 54 | mv xmpp-web image-build/xmpp-web && 55 | cp -a docs/docker image-build/docs/ -r 56 | 57 | - name: Set up QEMU 58 | uses: docker/setup-qemu-action@v3 59 | 60 | - name: Set up Docker Buildx 61 | uses: docker/setup-buildx-action@v3 62 | 63 | - name: Login to DockerHub 64 | uses: docker/login-action@v3 65 | with: 66 | username: ${{ secrets.DOCKERHUB_USERNAME }} 67 | password: ${{ secrets.DOCKERHUB_TOKEN }} 68 | 69 | - name: Build and push docker multi architecture image 70 | id: docker_build 71 | uses: docker/build-push-action@v5 72 | with: 73 | context: image-build 74 | file: docs/docker/Dockerfile-multiarch 75 | push: true 76 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 77 | build-args: | 78 | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 79 | GIT_COMMIT=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 80 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 81 | tags: ${{ steps.meta.outputs.tags }} 82 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Release version 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Read metadata 13 | uses: docker/metadata-action@v5 14 | id: meta 15 | with: 16 | images: nioc/xmpp-web 17 | tags: | 18 | type=semver,pattern={{version}} 19 | flavor: | 20 | latest=true 21 | 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Generate changelog from git commits 28 | id: git-changelog 29 | run: | 30 | echo 'changelog<> $GITHUB_OUTPUT 31 | git log $(git tag |tail -2 | head -1).. --pretty=format:"- %s %n%w(150,2,2)%b" >> $GITHUB_OUTPUT 32 | echo 'EOF' >> $GITHUB_OUTPUT 33 | 34 | - name: Setup Node.js 20 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20.x 38 | cache: 'npm' 39 | cache-dependency-path: 'package-lock.json' 40 | 41 | - name: Build front 42 | run: npm ci && npm run build 43 | 44 | - name: Create release archive 45 | shell: bash 46 | run: | 47 | mv dist xmpp-web && 48 | tar zcvf xmpp-web-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}.tar.gz xmpp-web 49 | 50 | - name: Draft Github release with archive 51 | uses: softprops/action-gh-release@v2 52 | with: 53 | draft: true 54 | files: xmpp-web-${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}.tar.gz 55 | body: | 56 | ### Commits 57 | 58 | ${{ steps.git-changelog.outputs.changelog }} 59 | 60 | ### :warning: Breaking changes 61 | 62 | ### :bulb: Features 63 | 64 | ### :beetle: Bug fixes 65 | 66 | ### :wrench: Technical 67 | 68 | - name: Prepare docker build context 69 | shell: bash 70 | run: | 71 | mkdir -p image-build/docs && 72 | mv xmpp-web image-build/xmpp-web && 73 | cp -a docs/docker image-build/docs/ -r 74 | 75 | - name: Set up QEMU 76 | uses: docker/setup-qemu-action@v3 77 | 78 | - name: Set up Docker Buildx 79 | uses: docker/setup-buildx-action@v3 80 | 81 | - name: Login to DockerHub 82 | uses: docker/login-action@v3 83 | with: 84 | username: ${{ secrets.DOCKERHUB_USERNAME }} 85 | password: ${{ secrets.DOCKERHUB_TOKEN }} 86 | 87 | - name: Build and push docker multi architecture image 88 | id: docker_build 89 | uses: docker/build-push-action@v5 90 | with: 91 | context: image-build 92 | file: docs/docker/Dockerfile-multiarch 93 | push: true 94 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 95 | build-args: | 96 | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 97 | GIT_COMMIT=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 98 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 99 | tags: ${{ steps.meta.outputs.tags }} -------------------------------------------------------------------------------- /.github/workflows/e2e-test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and E2E tests 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | lint-and-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup Node.js 20 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20.x 22 | cache: 'npm' 23 | cache-dependency-path: 'package-lock.json' 24 | 25 | - name: Install dependencies 26 | uses: cypress-io/github-action@v6 27 | with: 28 | runTests: false 29 | 30 | - name: Lint 31 | run: npm run lint 32 | 33 | - name: E2E tests 34 | uses: cypress-io/github-action@v6 35 | with: 36 | install: false 37 | config: baseUrl=http://localhost:3000/ 38 | start: npm run dev 39 | wait-on: 'http://localhost:3000' 40 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint code 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Setup Node.js 20 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.x 19 | cache: 'npm' 20 | cache-dependency-path: 'package-lock.json' 21 | 22 | - name: Install and lint 23 | run: npm ci && npm run lint 24 | -------------------------------------------------------------------------------- /.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 | # Test and coverage files 15 | cypress/screenshots/ 16 | coverage 17 | .nyc_output 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | xmpp-web.code-workspace 28 | 29 | # Local certificates 30 | certs/* 31 | !certs/.gitkeep 32 | stats.html 33 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save=true 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": ["html", "text"] 3 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | The project is open and any contribution is welcome! 4 | 5 | There are many ways to contribute to the XMPP Web such as reporting bugs, suggesting features and submitting pull requests. 6 | 7 | ### Developers guidelines 8 | 9 | #### Discuss first 10 | 11 | Before writing code / submitting pull request, please [create a feature request](https://github.com/nioc/xmpp-web/issues/new?assignees=&labels=enhancement&template=feature_request.yml) in order to discuss your idea. 12 | 13 | #### Code style 14 | 15 | When writing some code, lint it with [provided rules](.eslintrc.cjs): `npm run lint` (your pull request will not be merged until checks succeed). 16 | 17 | #### Commits message 18 | 19 | Read [conventional commits](https://www.conventionalcommits.org/) and write your commit messages accordingly. 20 | You can add provided git `commit-msg` [hook](docs/git-hooks/commit-msg) (which execute a basic message checking) with the following command: `npm run configure-git-hook`. 21 | 22 | #### Architecture 23 | 24 | Application is build around 4 components: 25 | - [XmppClient.js](src/services/XmppClient.js) which is responsible for low-level XMPP logic (connect, parse stanza, ...) it relies on [xmpp.js](https://github.com/xmppjs/xmpp.js), 26 | - [XmppSocket.js](src/services/XmppSocket.js) which is responsible for application level XMPP logic, 27 | - [Pinia store](src/store/index.js) which, as its name implies, store the data (contacts, rooms, messages, ...) for the entire application (Vue components read data through it), 28 | - Vue components which include display and interaction logic of their own. 29 | 30 | ### Edit frontend code (VueJS) 31 | 32 | This app is build using Vue 3, you can found useful information on [documentation](https://vuejs.org/guide/essentials/template-syntax.html). 33 | 34 | The UI part is using [Oruga](https://oruga.io/documentation/) library. 35 | 36 | #### 1. Fork and clone repo 37 | 38 | In order to contribute to this application, you need to: 39 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the repo and clone it in folder of your choice (in example `/var/www/xmpp-web`): `git clone https://github.com//xmpp-web.git /var/www/xmpp-web` 40 | 2. Access the frontend folder in a shell `cd /var/www/xmpp-web` for next steps (2a or 2b, then 3) 41 | 42 | #### 2-a. Dev tools by installing prerequisite... 43 | 44 | 1. install prerequisite: 45 | - [Node.js](https://nodejs.org/) 46 | - npm `npm install npm@latest -g` 47 | 2. Build the project `npm install` and wait for the downloads 48 | 4. Start the vite server `npm run dev` 49 | 50 | 51 | #### 2.b. ...or dev tools by using Dockerfile-dev 52 | 53 | If you are using Docker, you can use the [Dockerfile-dev](/docs/docker/Dockerfile-dev) to avoid installing Node.js on your local computer: 54 | 55 | 1. Build dev image: 56 | ``` 57 | docker build \ 58 | -f docs/docker/Dockerfile-dev \ 59 | -t nioc/xmpp-web:node-alpine-dev \ 60 | . 61 | ``` 62 | 63 | 2. Run run vite server in container: 64 | ``` 65 | docker run -it \ 66 | -p 3000:3000 \ 67 | --rm \ 68 | -v "$(pwd)":/app \ 69 | -v "/app/node_modules" \ 70 | --name xmpp-web-1 \ 71 | nioc/xmpp-web:node-alpine-dev 72 | ``` 73 | 74 | #### 3. Do your work 75 | 76 | 1. Open your browser on http://localhost:3000 (vite handle hot reload after changes) 77 | 2. Edit `public/local.js` according to your XMPP server configuration (you can add it in `.gitignore` file to avoid to restore it before commit) 78 | 3. Edit the code (I suggest using [Visual Studio Code](https://code.visualstudio.com/download) with [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)) 79 | 4. When you are satisfied with your work, run the linter `npm run lint` 80 | 5. Create a dedicated branch with explicit feature name `git checkout -b feat#123-my_awesome_feature` 81 | 6. Commit your changes (with a detailled message, using [conventional commits](https://www.conventionalcommits.org/)): `git commit -am 'feat(guest): Add an awesome feature for guest' -m 'Close #123'` 82 | 7. Push to the branch: `git push origin feat#123-my_awesome_feature` 83 | 8. Submit a pull request 84 | 85 | ### XMPP server backend 86 | 87 | You can find some docker XMPP server setups in this [folder](/docs/staging-environments). 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMPP Web 2 | 3 | [![license: AGPLv3](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) 4 | [![GitHub release](https://img.shields.io/github/release/nioc/xmpp-web.svg)](https://github.com/nioc/xmpp-web/releases/latest) 5 | [![GitHub downloads](https://img.shields.io/github/downloads/nioc/xmpp-web/total?label=github%20downloads)](https://github.com/nioc/xmpp-web/releases/latest) 6 | [![GitHub Docker workflow status](https://img.shields.io/github/actions/workflow/status/nioc/xmpp-web/docker-image.yml?label=github%20build)](https://github.com/nioc/xmpp-web/actions/workflows/docker-image.yml) 7 | [![GitHub E2E tests workflow status](https://img.shields.io/github/actions/workflow/status/nioc/xmpp-web/e2e-test.yml?label=e2e%20tests)](https://github.com/nioc/xmpp-web/actions/workflows/e2e-test.yml) 8 | [![Docker Pulls](https://img.shields.io/docker/pulls/nioc/xmpp-web)](https://hub.docker.com/r/nioc/xmpp-web/tags) 9 | [![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/nioc/xmpp-web?sort=date)](https://hub.docker.com/r/nioc/xmpp-web/tags) 10 | [![GitHub issues by-label](https://img.shields.io/github/issues/nioc/xmpp-web/help%20wanted?label=issues%20need%20help)](https://github.com/nioc/xmpp-web/labels/help%20wanted) 11 | 12 | Lightweight web chat client for XMPP server. 13 | 14 | ## Table of contents 15 | 16 | - [Key features](#key-features) 17 | - [Installation](#installation) 18 | - [Ansible](#ansible) 19 | - [Archive](#archive) 20 | - [Docker image](#docker-image) 21 | - [Build from source](#build-from-source) 22 | - [Upgrade](#upgrade) 23 | - [Archive](#archive) 24 | - [Docker image](#docker-image) 25 | - [Configuration](#configuration) 26 | - [Contributing](#contributing) 27 | - [Credits](#credits) 28 | - [License](#license) 29 | 30 | ## Key features 31 | 32 | - Connect to an XMPP server with WebSocket, 33 | - Chat and groupchat (MUC as defined in XEP-0045), 34 | - Retrieve contacts (roster) and bookmarked rooms (XEP-0048), 35 | - Send and receive files over HTTP (XEP-0066, XEP-0363), 36 | - Handle password protected room, 37 | - Display and send chat state notifications: is composing, is paused (XEP-0085), 38 | - Format messages: bold, italic, striked, link and code inline/block (XEP-0393), 39 | - Pick emoji, 40 | - Room creation and configuration, 41 | - Apply message moderation (XEP-0425), 42 | - Display and edit vCard information (in particular the user avatar, XEP-0054), 43 | - PWA (Progressive Web App) creating user experiences similar to native applications on desktop and mobile devices, 44 | - Lightweight (600 KB gzipped at the first loading and then less than 10 KB) 45 | - Guest access `/guest?join={jid}` (joining a MUC anonymously as described in RFC 4505) 46 | 47 | ![Screenshot desktop](/docs/screenshot-desktop-main.png) 48 | ![Screenshot mobile home](/docs/screenshot-mobile-main.png) ![Screenshot mobile chat](/docs/screenshot-mobile-chat.png) 49 | ![Screenshot guest join](/docs/screenshot-guest-join.png) 50 | 51 | ## Installation 52 | 53 | There are 4 different ways to install XMPP Web: 54 | 55 | - using [Docker image](#docker-image) (easiest, **strongly recommended**), 56 | - using [release archive](#archive), 57 | - build from [source code](#build-from-source) (latest code but hardest). 58 | - ~~from provided [Ansible role](#ansible)~~ (not maintained), 59 | 60 | ### Ansible 61 | 62 | Long time ago, we provided an [Ansible role](/docs/ansible/xmpp-web/README.md). 63 | It had not been updated since 2020 and nobody used it, **it is not maintained anymore**. 64 | You can still use it but it will not set all the relevant configuration in `local.js`. 65 | 66 | ### Archive 67 | 68 | You can download latest `.tar.gz` archive, unpack files in web directory and configure: 69 | 70 | - download [latest release](https://github.com/nioc/xmpp-web/releases/latest), 71 | - unarchive, 72 | - set files owner (example: `www-data` with Apache), 73 | - create [Apache virtual host](/docs/apache.conf), 74 | - configure [`local.js`](public/local.js). 75 | 76 | ``` bash 77 | wget https://github.com/nioc/xmpp-web/releases/latest/download/xmpp-web-0.9.7.tar.gz \ 78 | -O /var/tmp/xmpp-web.tar.gz \ 79 | && cd /var/www \ 80 | && tar -xvzf /var/tmp/xmpp-web.tar.gz \ 81 | && chown www-data /var/www/xmpp-web/ -R 82 | ``` 83 | 84 | ### Docker image 85 | 86 | On each release, we also build a [Docker image](https://hub.docker.com/r/nioc/xmpp-web) which is the latest stable Nginx (Alpine variant in order to keep lightweight) serving the generated assets. Configuration in `local.js` is set up according to environment variables (names and meanings are explained in [configuration](#configuration) section). 87 | 88 | This can be used: 89 | - as standalone service: 90 | ``` bash 91 | docker run -it -p 80:80 --rm \ 92 | -e XMPP_WS=https://domain-xmpp.ltd:5281/xmpp-websocket \ 93 | -e APP_DEFAULT_DOMAIN=domain-xmpp.ltd \ 94 | --name xmpp-web-1 nioc/xmpp-web 95 | ``` 96 | 97 | - in a `docker-compose.yml` file: 98 | ``` yml 99 | version: "3.4" 100 | services: 101 | xmpp-web: 102 | image: nioc/xmpp-web:latest 103 | ports: 104 | - "80:80" 105 | environment: 106 | - XMPP_WS=https://domain-xmpp.ltd:5281/xmpp-websocket 107 | - APP_DEFAULT_DOMAIN=domain-xmpp.ltd 108 | ``` 109 | 110 | ### Build from source 111 | 112 | If you want the latest code without waiting for the next release, you can clone this repo, build assets and copy `dist` files in web directory: 113 | ``` bash 114 | git clone https://github.com/nioc/xmpp-web.git xmpp-web 115 | cd xmpp-web 116 | npm ci 117 | npm run build 118 | nano dist/local.js 119 | mv dist /var/www/xmpp-web 120 | chown www-data /var/www/xmpp-web/ -R 121 | ``` 122 | 123 | ## Upgrade 124 | 125 | ### Archive 126 | 127 | Use the same method as installation or use the [update.sh](docs/update.sh) script. After that, do not forget to edit `local.js`. 128 | 129 | ### Docker image 130 | 131 | Use `docker pull nioc/xmpp-web:latest` and check if there is some new environment variables to set. 132 | 133 | ## Configuration 134 | 135 | | `local.js` attribute | Environment (Docker) | Default (initial value) | Description 136 | | ------------------------- |----------------------------------| ---------------------------------------------|--------------------------- 137 | | `name` | `APP_NAME` | `"XMPP web"` | Application name 138 | | `transports.websocket` | `APP_WS` | `"wss://chat.domain-web.ltd/xmpp-websocket"` | Websocket endpoint used by application (proxy or direct XMPP server) 139 | | `hasRegisteredAccess` | `APP_REGISTERED_ACCESS` | `true` | Set to `false` to disable registered users components (guest access only) 140 | | `hasGuestAccess` | `APP_GUEST_ACCESS` | `true` | Set to `false` to disable guest users components 141 | | `anonymousHost` | `XMPP_ANON_HOST` | `null` | Virtual host used for guest access (anonymous) 142 | | `isTransportsUserAllowed` | `APP_IS_TRANSPORTS_USER_ALLOWED` | `false` | Allow user to set endpoints on the fly in login component 143 | | `hasHttpAutoDiscovery` | `APP_HTTP_AUTODISCOVERY` | `false` | Allow to retrieve a `.well-known/host-meta.json` if user log on a different domain 144 | | `resource` | `APP_RESOURCE` | `"Web XMPP"` | Resource (client) affected to user 145 | | `defaultDomain` | `APP_DEFAULT_DOMAIN` | `"domain-xmpp.ltd"` | Domain used if user do not provide a full jid 146 | | `defaultMuc` | `APP_DEFAULT_MUC` | `null` | Autocomplete MUC address (ex: `conference.domain.ltd`) if user do not provide a full room jid (join & create) 147 | | `isStylingDisabled` | `APP_IS_STYLING_DISABLED` | `false` | Set to `true` for disable messages styling 148 | | `hasSendingEnterKey` | `APP_HAS_SENDING_ENTER_KEY` | `false` | If `true`, `Enter` key sends message, it adds new line otherwise (`Control`+`Enter` always sends message) 149 | | `connectTimeout` | `XMPP_CONNECT_TIMEOUT` | `5000` | Timeout in ms before XMPP connection is considered as rejected 150 | | `pinnedMucs` | `APP_PINNED_MUCS` | `[]` | Jid MUC list to hightlight in guest rooms page, ex: `['welcome@conference.domain.ltd', 'vip@conference.domain.ltd']` 151 | | `logoUrl` | `APP_LOGO_URL` | `''` | Custom logo URL for login/guest pages 152 | | `guestDescription` | `APP_GUEST_DESCRIPTION` | `''` | Welcome text for guests (allows some HTML tags like `

`, ``, ``, see [allowed tags list](https://www.npmjs.com/package/sanitize-html#default-options)) 153 | | `sso.endpoint` | `APP_SSO_ENDPOINT` | `false` | Public endpoint for SSO (must be secured: do not expose it without an authentication mechanism) 154 | | `sso.jidHeader` | `APP_SSO_JID_HEADER` | `"jid"` | Header containing JID after SSO request 155 | | `sso.passwordHeader` | `APP_SSO_PASSWORD_HEADER` | `"password"` | Header containing password after SSO request 156 | | N/A | `XMPP_WS` | `''` | Websocket endpoint proxyfied by Nginx (on a docker installation) 157 | 158 | ## Contributing 159 | 160 | If you have a suggestion for a feature you think would enhance this product, please submit a [feature request](https://github.com/nioc/xmpp-web/issues/new?labels=enhancement&template=feature_request.yml). 161 | Pull requests are welcomed (please create feature request for discussing it before), see [contributing](CONTRIBUTING.md). 162 | 163 | ## Credits 164 | 165 | - **[Nioc](https://github.com/nioc/)** - _Initial work_ 166 | 167 | See also the list of [contributors](https://github.com/nioc/xmpp-web/contributors) to this project. 168 | 169 | This project is powered by the following components: 170 | - [xmpp.js](https://github.com/xmppjs/xmpp.js) (ISC) 171 | - [Vue.js](https://vuejs.org/) (MIT) 172 | - [Pinia](https://pinia.vuejs.org/) (MIT) 173 | - [Vue Router](https://router.vuejs.org/) (MIT) 174 | - [Day.js](https://day.js.org/) (MIT) 175 | - [Bulma](https://bulma.io/) (MIT) 176 | - [Oruga](https://oruga.io/) (MIT) 177 | - [Fork Awesome](https://forkaweso.me) (SIL OFL 1.1) 178 | 179 | ## License 180 | 181 | This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE.md) file for details 182 | -------------------------------------------------------------------------------- /certs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/certs/.gitkeep -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | testIsolation: false, 6 | specPattern: 'cypress/e2e/*.js', 7 | setupNodeEvents(on, config) { 8 | return require('@cypress/code-coverage/task')(on, config) 9 | }, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /cypress/e2e/app.js: -------------------------------------------------------------------------------- 1 | /// 2 | /* eslint-disable no-undef */ 3 | 4 | import { MockServer } from '../support/mock-socket' 5 | import { WebSocket } from 'mock-socket' 6 | import { version } from '../../package.json' 7 | 8 | let url = 'wss://chat.domain-web.ltd/xmpp-websocket' 9 | const jidLocal = 'admin' 10 | let jidDomain = 'domain-xmpp.ltd' 11 | 12 | let mockServer 13 | 14 | describe('XMPP Web for registered users', () => { 15 | before(() => { 16 | cy.clearAllCookies() 17 | .clearAllLocalStorage() 18 | .clearAllSessionStorage() 19 | .visit('/', { 20 | onBeforeLoad: (win) => { 21 | // stub websocket requests 22 | cy.stub(win, 'WebSocket').callsFake(url => new WebSocket(url)) 23 | }, 24 | onLoad: (win) => { 25 | // get config 26 | url = win.config.transports.websocket 27 | jidDomain = win.config.defaultDomain 28 | mockServer = MockServer({ url, jidLocal, jidDomain }) 29 | }, 30 | }) 31 | }) 32 | 33 | describe('Login', function () { 34 | it('displays login page with submit disabled', () => { 35 | cy.get('input[name="jid"]').should('have.attr', 'placeholder', `username@${jidDomain}`) 36 | cy.get('input[name="password"]').should('have.attr', 'placeholder', 'Password') 37 | cy.get('button[type="submit"]').should('be.disabled') 38 | }) 39 | it('can login and route to main page', () => { 40 | cy.get('input[name="jid"]').type(jidLocal) 41 | .get('button[type="submit"]').should('be.disabled') 42 | .get('input[name="password"]').type('pwd') 43 | .get('button[type="submit"]').should('be.enabled') 44 | .get('button[type="submit"]').click() 45 | .hash().should('eq', '#/') 46 | .get('#navbar-menu', { includeShadowDom: true }).should('contain', `${jidLocal}@${jidDomain}`) 47 | }) 48 | it('does not store credentials', () => { 49 | cy.visit('/', { 50 | onBeforeLoad: (win) => { 51 | cy.stub(win, 'WebSocket').callsFake(url => new WebSocket(url)) 52 | }, 53 | }) 54 | .hash().should('eq', '#/login?redirect=/') 55 | }) 56 | it('can login and store credentials', () => { 57 | cy.get('input[name="jid"]').clear().type('myuser') 58 | .get('input[name="password"]').type('pwd') 59 | .get('input[type="checkbox"]').check() 60 | .get('button[type="submit"]').click() 61 | .hash().should('eq', '#/') 62 | .get('#navbar-menu', { includeShadowDom: true }).should('contain', `${jidLocal}@${jidDomain}`) 63 | cy.visit('/', { 64 | onBeforeLoad: (win) => { 65 | cy.stub(win, 'WebSocket').callsFake(url => new WebSocket(url)) 66 | }, 67 | }) 68 | .hash().should('eq', '#/') 69 | }) 70 | }) 71 | 72 | describe('Contacts', function () { 73 | it('displays contacts', () => { 74 | cy.get('#contactsList li').should('have.length', 1) 75 | .get('#contactsList li').first().should('have.text', `user2@${jidDomain}`) 76 | }) 77 | it('can open conversation from contact and load history', () => { 78 | cy.get('#contactsList li').first().click() 79 | .get('#app').find('.sendbox').should('exist') 80 | .get('#messages-container').should('contain', 'Previous message') 81 | }) 82 | }) 83 | 84 | describe('Groups', function () { 85 | it('displays groups', () => { 86 | cy.get('#groupsList li').should('have.length', 1) 87 | .get('#groupsList li').first().should('have.text', 'Dev Team') 88 | }) 89 | it('displays display contact in a group', () => { 90 | cy.get('#groupsList li:first-child a').click() 91 | .get('#groupsList li:first-child ul li').first().should('have.text', `user2@${jidDomain}`) 92 | }) 93 | it('can open conversation from contact and load history', () => { 94 | cy.get('#contactsList li').first().click() 95 | .get('#app').find('.sendbox').should('exist') 96 | .get('#messages-container').should('contain', 'Previous message') 97 | }) 98 | }) 99 | 100 | describe('Chat', function () { 101 | it('can type message', () => { 102 | cy.get('.sendbox textarea').type('Hello, this is a new message') 103 | }) 104 | it('can select an emoji', () => { 105 | cy.get('.sendbox button:not([type="submit"])[title="Choose an emoji"]').click() 106 | .get('.sendbox .emojiPicker [role="tab"]:first').next().click() 107 | .get('.sendbox .emojiPicker a[title="writing hand"]').click() 108 | .get('.sendbox button[type="submit"]').click() 109 | }) 110 | it('display received message', () => { 111 | const msg = 'Hello, do you copy?' 112 | mockServer.emit('message', `${msg}`) 113 | cy.get('#messages-container').should('contain', msg) 114 | }) 115 | it('display inline code formatted', () => { 116 | const code = 'inline code' 117 | const msg = 'This is inline code: `' + code + '`' 118 | mockServer.emit('message', `${msg}`) 119 | cy.get('#messages-container div:last-child code').should('have.text', code) 120 | }) 121 | it('display code block formatted', () => { 122 | const code = 'code block' 123 | const msg = 'This is code block:\n``` js\n' + code + '\n```' 124 | mockServer.emit('message', `${msg}`) 125 | cy.get('#messages-container div:last-child pre code').should('have.text', code) 126 | }) 127 | }) 128 | 129 | describe('Rooms', function () { 130 | it('displays bookmarked rooms and a link to public rooms', () => { 131 | cy.get('#roomsList li').should('have.length', 5) 132 | .get('#roomsList li').first().should('have.text', 'welcome') 133 | .next().should('have.text', 'public') 134 | .next().should('have.text', 'Public rooms') 135 | .click() 136 | .hash().should('eq', '#/rooms/discover') 137 | .get('main table tbody tr').should('have.length', 2) 138 | .first().as('firstRoom').find('td').first().next().should('have.text', 'welcome') 139 | .next().should('have.text', '1') 140 | .next().next().next().next().next().should('have.descendants', 'i') 141 | }) 142 | it('can join room', () => { 143 | cy.get('#roomsList li').first().click() 144 | .hash().should('eq', `#/rooms/welcome@conference.${jidDomain}`) 145 | }) 146 | it('display received message on active room', () => { 147 | const msg = 'Hello room, do you copy?' 148 | mockServer.emit('message', `${msg}`) 149 | cy.get('#messages-container').should('contain', msg) 150 | }) 151 | it('display unread count on received message on inactive room', () => { 152 | const msg = 'Hello other room, read this later' 153 | mockServer.emit('message', `${msg}`) 154 | cy.get('#messages-container').should('not.contain', msg) 155 | .get('#roomsList li').first().next().find('a .tag').should('have.text', '1') 156 | }) 157 | it('can create room', () => { 158 | cy.get('#roomsList li').first().next().next().next().next().click() 159 | .hash().should('eq', '#/rooms/new') 160 | .get('main input[title="Enter room Jid"]').as('roomName').should('exist') 161 | .get('main button[type="submit"]').should('be.disabled') 162 | .get('@roomName').type(`newRoom@conference.@${jidDomain}`) 163 | .get('main button[type="submit"]').should('be.enabled') 164 | }) 165 | }) 166 | 167 | describe('About', function () { 168 | it('display application information', () => { 169 | cy.get('#navbar-burger').click() 170 | .get('#navbar-menu a[href="#/about"]').click() 171 | .get('#navbar-burger').click() 172 | .hash().should('eq', '#/about') 173 | .get('main .tags .tag:nth-child(2)').should('contain', version) 174 | }) 175 | }) 176 | 177 | describe('Logout', function () { 178 | it('logout from the server and return to login page', () => { 179 | cy.get('#navbar-burger').click() 180 | .get('#logout').click() 181 | .hash().should('eq', '#/login') 182 | }) 183 | }) 184 | }) 185 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/README.md: -------------------------------------------------------------------------------- 1 | Ansible Role: XMPP Web 2 | ====================== 3 | 4 | Install XMPP Web: 5 | - install apache (optional if you already use a webserver), 6 | - download archive, 7 | - setup apache virtual host, 8 | - configure local.js. 9 | 10 | Requirements 11 | ------------ 12 | 13 | Ansible >= 2.9. 14 | 15 | Role Variables 16 | -------------- 17 | 18 | These variables should be checked/updated before use: 19 | - `xmppweb_install_apache`: Does Apache should be installed, set `false` if already present (but check required modules), default : `true`, 20 | - `xmppweb_version`: version to install (see [latest](https://github.com/nioc/xmpp-web/releases/latest)), 21 | - `domain`: your domain name (not a role variable but **must be set** in your playbook/host), no default, 22 | - `xmppweb_domain`: subdomain used for your instance, default: `chat.{{domain}}`, 23 | - `use_web_proxy`: Using or not a proxy web like HAProxy (not a role variable but **must be set** in your playbook/host), no default, 24 | - `xmppweb_port`: Apache listening port (only if apache is behind a proxy with `use_web_proxy = true`), default: `8080`, 25 | - `xmppweb_rootpath`: Apache virtual host root path (where code will be unarchived), default: `/var/www`, 26 | - `xmppweb_webuser`: Linux user running Apache, default: `www-data`. 27 | 28 | These variables should not be updated: 29 | - `xmppweb_download_url`: url for downloading archive. 30 | 31 | Dependencies 32 | ------------ 33 | 34 | None. 35 | 36 | Example Playbook 37 | ---------------- 38 | 39 | - hosts: servers 40 | vars: 41 | domain: mydomain.ltd 42 | use_web_proxy: false 43 | roles: 44 | - name: xmpp-web 45 | xmppweb_domain: chat.mydomain.ltd 46 | xmppweb_port: 8081 47 | 48 | License 49 | ------- 50 | 51 | AGPL-3.0-or-later 52 | 53 | Author Information 54 | ------------------ 55 | 56 | This role was created in 2019 by [Nioc](https://github.com/nioc). 57 | -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | xmppweb_install_apache: true 3 | xmppweb_version: 0.4.1 4 | xmppweb_download_url: https://github.com/nioc/xmpp-web/releases/download/{{xmppweb_version}}/xmpp-web-{{xmppweb_version}}.tar.gz 5 | xmppweb_domain: chat.{{domain}} 6 | xmppweb_xmpp_server: '{{domain}}' 7 | xmppweb_xmpp_port: 5280 8 | xmppweb_xmpp_ws_url: xmpp-websocket 9 | xmppweb_xmpp_bosh_url: http-bind 10 | xmppweb_port: 8080 11 | xmppweb_rootpath: /var/www 12 | xmppweb_webuser: www-data -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: reload apache 3 | become: yes 4 | systemd: 5 | name: apache2 6 | state: reloaded 7 | 8 | - name: restart apache 9 | become: yes 10 | systemd: 11 | name: apache2 12 | state: restarted -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Nioc 3 | description: Install XMPP web 4 | issue_tracker_url: https://github.com/nioc/xmpp-web/issues 5 | license: license (AGPL-3.0-or-later) 6 | min_ansible_version: 2.9 7 | galaxy_tags: [] 8 | 9 | dependencies: [] 10 | -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/tasks/apache.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install and configure Apache 3 | become: yes 4 | apt: 5 | name: ['apache2'] 6 | state: present 7 | cache_valid_time: 3600 8 | 9 | - name: Enable Apache modules 10 | become: yes 11 | apache2_module: 12 | name: '{{item}}' 13 | state: present 14 | with_items: ['proxy_wstunnel', 'proxy', 'proxy_http', 'rewrite', 'headers', 'ssl'] 15 | notify: restart apache 16 | 17 | - name: Make sure Apache is started and enabled to start on boot 18 | become: yes 19 | systemd: 20 | name: apache2 21 | state: started 22 | enabled: yes -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install and configure Apache 3 | include_tasks: apache.yml 4 | when: xmppweb_install_apache 5 | 6 | - name: Creates XMPP Web folder 7 | become: yes 8 | file: 9 | path: '{{xmppweb_rootpath}}/xmpp-web' 10 | state: directory 11 | owner: '{{xmppweb_webuser}}' 12 | group: '{{xmppweb_webuser}}' 13 | 14 | - name: Download and unarchive XMPP Web code 15 | become: yes 16 | unarchive: 17 | src: '{{xmppweb_download_url}}' 18 | dest: '{{xmppweb_rootpath}}' 19 | owner: '{{xmppweb_webuser}}' 20 | group: '{{xmppweb_webuser}}' 21 | remote_src: yes 22 | 23 | - name: Replace hostname in local.js file 24 | become: yes 25 | replace: 26 | path: '{{xmppweb_rootpath}}/xmpp-web/local.js' 27 | regexp: '{{item.regexp}}' 28 | replace: '{{item.replace}}' 29 | with_items: 30 | - regexp: domain-xmpp.ltd 31 | replace: '{{xmppweb_xmpp_server}}' 32 | - regexp: chat.domain-web.ltd 33 | replace: '{{xmppweb_domain}}' 34 | 35 | - name: Create XMPP Web Apache virtual host (using web proxy) 36 | when: use_web_proxy 37 | block: 38 | - name: Create XMPP Web Apache virtual hosts 39 | become: yes 40 | template: 41 | src: '024-chat-proxy.conf.j2' 42 | dest: /etc/apache2/sites-available/024-chat.conf 43 | 44 | - name: Enable XMPP Web Apache virtual host 45 | become: yes 46 | command: a2ensite 024-chat 47 | notify: reload apache 48 | 49 | - name: Create XMPP Web Apache virtual host 50 | when: not use_web_proxy 51 | block: 52 | - name: Create XMPP Web Apache virtual hosts 53 | become: yes 54 | template: 55 | src: '{{item}}.conf.j2' 56 | dest: /etc/apache2/sites-available/{{item}}.conf 57 | with_items: 58 | - 024-chat 59 | - 024-chat-ssl 60 | 61 | - name: Enable XMPP Web Apache virtual host 62 | become: yes 63 | command: a2ensite {{item}} 64 | with_items: 65 | - 024-chat 66 | - 024-chat-ssl 67 | notify: reload apache -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/templates/024-chat-proxy.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | ServerName {{xmppweb_domain}} 4 | ServerAlias {{xmppweb_domain}} 5 | ServerAdmin webmaster@{{domain}} 6 | 7 | DocumentRoot {{xmppweb_rootpath}}/xmpp-web 8 | 9 | # websocket proxy 10 | 11 | 12 | #ProxyPreserveHost On 13 | ProxyPass "ws://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_ws_url}}" 14 | 15 | 16 | 17 | # bosh proxy 18 | 19 | 20 | Header set Access-Control-Allow-Origin "*" 21 | Header set Access-Control-Allow-Headers "*" 22 | ProxyPass "http://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_bosh_url}}" 23 | ProxyPassReverse "http://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_bosh_url}}" 24 | 25 | 26 | 27 | # front files 28 | 29 | Options -Indexes +FollowSymLinks +MultiViews 30 | AllowOverride None 31 | Require all granted 32 | 33 | RewriteEngine On 34 | RewriteBase / 35 | RewriteRule ^index\.html$ - [L] 36 | RewriteCond %{REQUEST_FILENAME} !-f 37 | RewriteCond %{REQUEST_FILENAME} !-d 38 | RewriteRule . /index.html [L] 39 | 40 | 41 | 42 | ErrorLog ${APACHE_LOG_DIR}/chat_error.log 43 | LogLevel warn 44 | CustomLog ${APACHE_LOG_DIR}/chat_access.log vhost_combined 45 | 46 | -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/templates/024-chat-ssl.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | ServerName {{xmppweb_domain}} 4 | ServerAlias {{xmppweb_domain}} 5 | ServerAdmin webmaster@{{domain}} 6 | 7 | DocumentRoot {{xmppweb_rootpath}}/xmpp-web 8 | 9 | # websocket proxy 10 | 11 | 12 | #ProxyPreserveHost On 13 | ProxyPass "ws://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_ws_url}}" 14 | 15 | 16 | 17 | # bosh proxy 18 | 19 | 20 | Header set Access-Control-Allow-Origin "*" 21 | Header set Access-Control-Allow-Headers "*" 22 | ProxyPass "http://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_bosh_url}}" 23 | ProxyPassReverse "http://{{xmppweb_xmpp_server}}:{{xmppweb_xmpp_port}}/{{xmppweb_xmpp_bosh_url}}" 24 | 25 | 26 | 27 | # front files 28 | 29 | Options -Indexes +FollowSymLinks +MultiViews 30 | AllowOverride None 31 | Require all granted 32 | 33 | RewriteEngine On 34 | RewriteBase / 35 | RewriteRule ^index\.html$ - [L] 36 | RewriteCond %{REQUEST_FILENAME} !-f 37 | RewriteCond %{REQUEST_FILENAME} !-d 38 | RewriteRule . /index.html [L] 39 | 40 | 41 | 42 | ErrorLog ${APACHE_LOG_DIR}/chat_error.log 43 | LogLevel warn 44 | CustomLog ${APACHE_LOG_DIR}/chat_access.log vhost_combined 45 | 46 | Protocols h2 http/1.1 47 | SSLEngine on 48 | SSLStrictSNIVHostCheck on 49 | Header always set Strict-Transport-Security "max-age=15768000" 50 | 51 | SSLCertificateFile /etc/letsencrypt/live/{{domain}}/fullchain.pem 52 | SSLCertificateKeyFile /etc/letsencrypt/live/{{domain}}/privkey.pem 53 | 54 | -------------------------------------------------------------------------------- /docs/ansible/xmpp-web/templates/024-chat.conf.j2: -------------------------------------------------------------------------------- 1 | # {{ ansible_managed }} 2 | 3 | ServerName {{xmppweb_domain}} 4 | ServerAlias {{xmppweb_domain}} 5 | ServerAdmin webmaster@{{domain}} 6 | Redirect permanent / https://{{xmppweb_domain}}/ 7 | 8 | ErrorLog ${APACHE_LOG_DIR}/chat_error.log 9 | LogLevel warn 10 | CustomLog ${APACHE_LOG_DIR}/chat_access.log vhost_combined 11 | 12 | -------------------------------------------------------------------------------- /docs/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | # virtual host identification 3 | ServerName chat.domain-web.ltd 4 | ServerAlias chat.domain-web.ltd 5 | ServerAdmin webmaster@domain-web.ltd 6 | 7 | Protocols h2 http/1.1 8 | 9 | DocumentRoot /var/www/xmpp-web 10 | 11 | # ssl 12 | SSLEngine on 13 | Header always set Strict-Transport-Security "max-age=15768000" 14 | SSLStrictSNIVHostCheck on 15 | 16 | # websocket proxy 17 | 18 | 19 | #ProxyPreserveHost On 20 | ProxyPass "ws://domain-xmpp.ltd:5280/xmpp-websocket" 21 | 22 | 23 | 24 | # front files 25 | 26 | Options -Indexes +FollowSymLinks +MultiViews 27 | AllowOverride None 28 | Require all granted 29 | 30 | RewriteEngine On 31 | RewriteBase / 32 | RewriteRule ^index\.html$ - [L] 33 | RewriteCond %{REQUEST_FILENAME} !-f 34 | RewriteCond %{REQUEST_FILENAME} !-d 35 | RewriteRule . /index.html [L] 36 | 37 | 38 | 39 | # logs 40 | ErrorLog ${APACHE_LOG_DIR}/chat_error.log 41 | LogLevel warn 42 | CustomLog ${APACHE_LOG_DIR}/chat_access.log vhost_combined 43 | 44 | -------------------------------------------------------------------------------- /docs/docker/998-update-local.js.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | update_localjs() { 6 | 7 | localjs=/usr/share/nginx/html/local.js 8 | 9 | if [ ! -w $localjs ]; then 10 | echo "local.js file is not writable" 11 | exit 1 12 | fi 13 | 14 | echo -n "update local.js with environment variables... " 15 | 16 | if [ "$APP_NAME" != "" ]; then 17 | sed -i -r "s|name: 'XMPP web'|name: '$APP_NAME'|g" $localjs 18 | fi 19 | 20 | sed -i -r "s|websocket: 'wss://chat.domain-web.ltd/xmpp-websocket'|websocket: '$APP_WS'|g" $localjs 21 | 22 | if [ "$APP_REGISTERED_ACCESS" != "1" ]; then 23 | sed -i -r "s|hasRegisteredAccess: true|hasRegisteredAccess: false|g" $localjs 24 | fi 25 | 26 | if [ "$APP_GUEST_ACCESS" != "1" ]; then 27 | sed -i -r "s|hasGuestAccess: true|hasGuestAccess: false|g" $localjs 28 | fi 29 | 30 | if [ "$XMPP_ANON_HOST" != "" ]; then 31 | sed -i -r "s|anonymousHost: null|anonymousHost: '$XMPP_ANON_HOST'|g" $localjs 32 | fi 33 | 34 | if [ "$APP_IS_TRANSPORTS_USER_ALLOWED" != "0" ]; then 35 | sed -i -r "s|isTransportsUserAllowed: false|isTransportsUserAllowed: true|g" $localjs 36 | fi 37 | 38 | if [ "$APP_HTTP_AUTODISCOVERY" != "0" ]; then 39 | sed -i -r "s|hasHttpAutoDiscovery: false|hasHttpAutoDiscovery: true|g" $localjs 40 | fi 41 | 42 | if [ "$APP_RESOURCE" != "" ]; then 43 | sed -i -r "s|resource: 'Web XMPP'|resource: '$APP_RESOURCE'|g" $localjs 44 | fi 45 | 46 | sed -i -r "s|defaultDomain: 'domain-xmpp.ltd'|defaultDomain: '$APP_DEFAULT_DOMAIN'|g" $localjs 47 | 48 | if [ "$APP_DEFAULT_MUC" != "" ]; then 49 | sed -i -r "s|defaultMuc: null|defaultMuc: '$APP_DEFAULT_MUC'|g" $localjs 50 | fi 51 | 52 | if [ "$APP_IS_STYLING_DISABLED" != "0" ]; then 53 | sed -i -r "s|isStylingDisabled: false|isStylingDisabled: true|g" $localjs 54 | fi 55 | 56 | if [ "$APP_HAS_SENDING_ENTER_KEY" != "0" ]; then 57 | sed -i -r "s|hasSendingEnterKey: false|hasSendingEnterKey: true|g" $localjs 58 | fi 59 | 60 | if [ "$APP_PINNED_MUCS" != "" ]; then 61 | sed -i -r "s|pinnedMucs: \[\]|pinnedMucs: $APP_PINNED_MUCS|g" $localjs 62 | fi 63 | 64 | if [ "$APP_LOGO_URL" != "" ]; then 65 | sed -i -r "s|logoUrl: ''|logoUrl: '$APP_LOGO_URL'|g" $localjs 66 | fi 67 | 68 | if [ "$APP_GUEST_DESCRIPTION" != "" ]; then 69 | sed -i -r "s|guestDescription: ''|guestDescription: '$APP_GUEST_DESCRIPTION'|g" $localjs 70 | fi 71 | 72 | if [ "$XMPP_CONNECT_TIMEOUT" != "" ]; then 73 | sed -i -r "s|connectTimeout: 5000|connectTimeout: $XMPP_CONNECT_TIMEOUT|g" $localjs 74 | fi 75 | 76 | if [ "$APP_SSO_ENDPOINT" != "" ]; then 77 | sed -i -r "s|endpoint: false|endpoint: '$APP_SSO_ENDPOINT'|g" $localjs 78 | fi 79 | 80 | if [ "$APP_SSO_JID_HEADER" != "" ]; then 81 | sed -i -r "s|jidHeader: 'jid'|jidHeader: '$APP_SSO_JID_HEADER'|g" $localjs 82 | fi 83 | 84 | if [ "$APP_SSO_PASSWORD_HEADER" != "" ]; then 85 | sed -i -r "s|passwordHeader: 'password'|passwordHeader: '$APP_SSO_PASSWORD_HEADER'|g" $localjs 86 | fi 87 | 88 | echo "done" 89 | } 90 | 91 | update_localjs 92 | 93 | exit 0 -------------------------------------------------------------------------------- /docs/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM node:lts-alpine as build-stage 3 | RUN apk add git 4 | # copy package 5 | WORKDIR /app 6 | COPY package*.json ./ 7 | # install dependencies 8 | RUN npm ci 9 | # copy src and assets 10 | COPY . . 11 | # build 12 | RUN npm run build 13 | 14 | # production stage 15 | FROM nginx:stable-alpine as production-stage 16 | # copy html files 17 | COPY --from=build-stage /app/dist /usr/share/nginx/html 18 | # copy nginx conf 19 | COPY ./docs/docker/998-update-local.js.sh /docker-entrypoint.d/998-update-local.js.sh 20 | COPY ./docs/docker/default.conf.template /etc/nginx/templates/default.conf.template 21 | # set environment variables 22 | ENV APP_NAME= 23 | ENV APP_WS=ws://localhost/xmpp-websocket 24 | ENV APP_REGISTERED_ACCESS=1 25 | ENV APP_GUEST_ACCESS=1 26 | ENV XMPP_ANON_HOST= 27 | ENV APP_IS_TRANSPORTS_USER_ALLOWED=0 28 | ENV APP_HTTP_AUTODISCOVERY=0 29 | ENV APP_RESOURCE= 30 | ENV APP_DEFAULT_DOMAIN=localhost 31 | ENV APP_DEFAULT_MUC= 32 | ENV APP_IS_STYLING_DISABLED=0 33 | ENV APP_HAS_SENDING_ENTER_KEY=0 34 | ENV APP_PINNED_MUCS= 35 | ENV APP_LOGO_URL= 36 | ENV APP_GUEST_DESCRIPTION= 37 | ENV APP_SSO_ENDPOINT= 38 | ENV APP_SSO_JID_HEADER= 39 | ENV APP_SSO_PASSWORD_HEADER= 40 | ENV XMPP_CONNECT_TIMEOUT= 41 | ENV XMPP_WS=http://localhost:5280/xmpp-websocket 42 | EXPOSE 80/tcp 43 | 44 | # Tag image 45 | ARG GIT_COMMIT=unspecified 46 | ARG BUILD_DATE 47 | ARG VERSION=unspecified 48 | LABEL org.label-schema.name="xmpp-web" 49 | LABEL org.label-schema.vendor="nioc" 50 | LABEL org.label-schema.license="AGPL-3.0-or-later" 51 | LABEL org.label-schema.vcs-url="https://github.com/nioc/xmpp-web" 52 | LABEL org.label-schema.vcs-ref=$GIT_COMMIT 53 | LABEL org.label-schema.build-date=$BUILD_DATE 54 | LABEL org.label-schema.version=$VERSION 55 | LABEL maintainer="nioc " 56 | -------------------------------------------------------------------------------- /docs/docker/Dockerfile-dev: -------------------------------------------------------------------------------- 1 | # this dockerfile allow to develop application code without installing NodeJS on local computer (note: node_modules will not be on local). 2 | 3 | # build image: `docker build -f docs/docker/Dockerfile-dev -t nioc/xmpp-web:node-alpine-dev .` 4 | # start image: `docker run -it -p 3000:3000 --rm -v "$(pwd)":/app -v "/app/node_modules" --name xmpp-web-1 nioc/xmpp-web:node-alpine-dev` 5 | 6 | FROM node:lts-alpine 7 | 8 | # install app dependencies 9 | RUN apk add git 10 | WORKDIR /app 11 | COPY package*.json ./ 12 | RUN npm install && chown node:node -R /app 13 | 14 | # start vite dev server 15 | EXPOSE 3000 16 | USER node 17 | CMD ["npm", "run", "dev"] 18 | -------------------------------------------------------------------------------- /docs/docker/Dockerfile-multiarch: -------------------------------------------------------------------------------- 1 | # Production stage (build was made previously) 2 | FROM nginx:stable-alpine 3 | # copy html files 4 | COPY xmpp-web /usr/share/nginx/html 5 | # copy nginx conf 6 | COPY ./docs/docker/998-update-local.js.sh /docker-entrypoint.d/998-update-local.js.sh 7 | COPY ./docs/docker/default.conf.template /etc/nginx/templates/default.conf.template 8 | # set environment variables 9 | ENV APP_NAME= 10 | ENV APP_WS=ws://localhost/xmpp-websocket 11 | ENV APP_REGISTERED_ACCESS=1 12 | ENV APP_GUEST_ACCESS=1 13 | ENV XMPP_ANON_HOST= 14 | ENV APP_IS_TRANSPORTS_USER_ALLOWED=0 15 | ENV APP_HTTP_AUTODISCOVERY=0 16 | ENV APP_RESOURCE= 17 | ENV APP_DEFAULT_DOMAIN=localhost 18 | ENV APP_DEFAULT_MUC= 19 | ENV APP_IS_STYLING_DISABLED=0 20 | ENV APP_HAS_SENDING_ENTER_KEY=0 21 | ENV APP_PINNED_MUCS= 22 | ENV APP_LOGO_URL= 23 | ENV APP_GUEST_DESCRIPTION= 24 | ENV APP_SSO_ENDPOINT= 25 | ENV APP_SSO_JID_HEADER= 26 | ENV APP_SSO_PASSWORD_HEADER= 27 | ENV XMPP_CONNECT_TIMEOUT= 28 | ENV XMPP_WS=http://localhost:5280/xmpp-websocket 29 | EXPOSE 80/tcp 30 | 31 | # Tag image 32 | ARG GIT_COMMIT=unspecified 33 | ARG BUILD_DATE 34 | ARG VERSION=unspecified 35 | LABEL org.label-schema.name="xmpp-web" 36 | LABEL org.label-schema.vendor="nioc" 37 | LABEL org.label-schema.license="AGPL-3.0-or-later" 38 | LABEL org.label-schema.vcs-url="https://github.com/nioc/xmpp-web" 39 | LABEL org.label-schema.vcs-ref=$GIT_COMMIT 40 | LABEL org.label-schema.build-date=$BUILD_DATE 41 | LABEL org.label-schema.version=$VERSION 42 | LABEL maintainer="nioc " 43 | -------------------------------------------------------------------------------- /docs/docker/default.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | include /etc/nginx/mime.types; 5 | 6 | # enable gzip 7 | gzip on; 8 | gzip_vary on; 9 | gzip_types text/plain text/css application/javascript; 10 | gzip_proxied any; 11 | 12 | # proxy XMPP websocket 13 | location /xmpp-websocket { 14 | proxy_pass ${XMPP_WS}; 15 | proxy_http_version 1.1; 16 | proxy_set_header Connection "Upgrade"; 17 | proxy_set_header Upgrade $http_upgrade; 18 | proxy_set_header Host $host; 19 | proxy_set_header X-Forwarded-For $remote_addr; 20 | proxy_read_timeout 2h; 21 | } 22 | 23 | # serve web app 24 | location / { 25 | root /usr/share/nginx/html; 26 | try_files $uri $uri/ /index.html; 27 | expires 30d; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /docs/git-hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Check the "conventional commits" message 4 | 5 | grep -E "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test|BREAKING CHANGE)(\([[:space:][:alnum:]-]+\))?!?: .*" "$1" >/dev/null; 6 | if [ $? != 0 ] 7 | then 8 | echo "Invalid conventional commit message" 9 | exit 1 10 | fi 11 | -------------------------------------------------------------------------------- /docs/screenshot-desktop-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/docs/screenshot-desktop-main.png -------------------------------------------------------------------------------- /docs/screenshot-guest-join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/docs/screenshot-guest-join.png -------------------------------------------------------------------------------- /docs/screenshot-mobile-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/docs/screenshot-mobile-chat.png -------------------------------------------------------------------------------- /docs/screenshot-mobile-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/docs/screenshot-mobile-main.png -------------------------------------------------------------------------------- /docs/staging-environments/README.MD: -------------------------------------------------------------------------------- 1 | # XMPP Web staging environments 2 | 3 | Each folder contains a `docker-compose.yml` file and server configurations in order to help for bug reproducing. 4 | 5 | | `service` | XMPP Server | Configuration | 6 | |-----------|--------------|-------------------------------------------------------| 7 | | prosody | Prosody 0.11 | [standard](prosody-std) with http upload, muc and mam | 8 | 9 | You can submit a pull request to add different servers or configurations. 10 | -------------------------------------------------------------------------------- /docs/staging-environments/REPRODUCING.MD: -------------------------------------------------------------------------------- 1 | # Reproducing bug or feature request 2 | 3 | You may have been asked to "*reproduce your problem*" with the [`need repro` issue label](https://github.com/nioc/xmpp-web/labels/need%20repro), so here is the operating procedure (you need to have Docker [Desktop](https://docs.docker.com/get-docker/) or [Engine](https://docs.docker.com/engine/install/) installed[^1]): 4 | 5 | - clone this repo: 6 | 7 | ``` bash 8 | git clone https://github.com/nioc/xmpp-web.git 9 | ``` 10 | and go to the `docs/staging-environments/prosody-std/` folder, 11 | - change the server [configuration `prosody.cfg.lua`](prosody-std/prosody.cfg.lua) to reflect your own[^2], 12 | - add necessary modules (if any) in the `docs/staging-environments/prosody-std/modules` folder, 13 | - change XMPP Web `local.js` environment variables in [docker-compose.yml](prosody-std/docker-compose.yml) file to reflect your own[^2], 14 | - start containers (press `Ctrl` + `C` when you are done): 15 | 16 | ``` bash 17 | docker-compose -f "docker-compose.yml" up \ 18 | && docker-compose -f 'docker-compose.yml' --project-name 'prosody-std' down 19 | ``` 20 | - create any necessary data (room, chat, user configuration, ...)[^2], 21 | - reproduce your issue, 22 | - archive the whole `docs/staging-environments/prosody-std/` folder: 23 | 24 | ``` bash 25 | tar zcvf ../my-reproducing-setup.tar.gz ../prosody-std 26 | ``` 27 | - add the created archive to your issue. 28 | 29 | :warning: Without this input, your issue will be dismissed (closed as *not planned*) by the stale bot after two weeks. 30 | 31 | [^1]: If you do not like Docker, you can always reproduce without it by using the same type of installation to produce the expected archive. 32 | [^2]: This may seem obvious, but: **do not use your real dns or any user credentials**. 33 | -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/README.MD: -------------------------------------------------------------------------------- 1 | # Prosody standard 2 | 3 | This setup a standard Prosody 0.11.9 with: 4 | - websocket listening on `http://*:5280/xmpp-websocket` (cross domain authorized for `http://localhost`, `https://localhost` and `http://localhost:300`), 5 | - standard virtual host `localhost`, 6 | - MUC service `conference.localhost` (with vCard support), 7 | - anonymous virtual host `anon.localhost`, 8 | - XEP-0065 proxy65 (file transfers) service `proxy.localhost`, 9 | - XEP-0313 mam (message archiving), 10 | - XEP-0363 [`http_upload`](https://modules.prosody.im/mod_http_upload) additional plugin, 11 | 12 | ## Before first use 13 | 14 | As [Prosody docker image](https://hub.docker.com/r/prosody/prosody) version is older than 0.12, we can not use plugin installer. 15 | 16 | In order to download extra plugins in the `modules` folder, you need to paste the following command in a shell (in `docs/staging-environments/prosody-std/` folder): 17 | 18 | ``` bash 19 | wget \ 20 | https://hg.prosody.im/prosody-modules/raw-file/f1f796e551f1/mod_http_upload/mod_http_upload.lua \ 21 | https://hg.prosody.im/prosody-modules/raw-file/tip/mod_vcard_muc/mod_vcard_muc.lua \ 22 | --directory-prefix modules 23 | ``` 24 | 25 | ## Docker compose 26 | 27 | To start both Prosody and XMPP web: 28 | 29 | ``` bash 30 | docker-compose -f "docker-compose.yml" up 31 | ``` 32 | 33 | To remove containers: 34 | ``` bash 35 | docker-compose -f 'docker-compose.yml' --project-name 'prosody-std' down 36 | ``` 37 | 38 | ## Users 39 | 40 | | Jid | Password | 41 | |-----------------|----------| 42 | | admin@localhost | admin | 43 | | user1@localhost | user1 | 44 | | user2@localhost | user2 | 45 | -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | 5 | prosody: 6 | image: prosody/prosody:0.11.9 7 | ports: 8 | - "5222:5222" # client to server connections 9 | - "5280:5280" # HTTP (including websocket) 10 | - "5000:5000" # file transfer proxy 11 | volumes: 12 | - ./prosody.cfg.lua:/etc/prosody/prosody.cfg.lua:ro # Prosody configuration 13 | - ./data:/var/lib/prosody # store Prosody data (ignored by git repo) 14 | - ./modules:/usr/lib/prosody/custom-modules 15 | - ./entrypoint.sh:/entrypoint.sh:ro # added to register user1 and user2 16 | environment: 17 | # the 3 following variables register admin@localhost, user1@localhost and user2@localhost users (password = JID local part) 18 | - LOCAL=admin 19 | - PASSWORD=admin 20 | - DOMAIN=localhost 21 | 22 | xmpp-web: 23 | image: nioc/xmpp-web:latest 24 | ports: 25 | - "80:80" 26 | # - "443:443" 27 | environment: 28 | - APP_NAME=XMPP Web Docker latest 29 | # - APP_WS=ws://localhost/xmpp-websocket #default 30 | - APP_REGISTERED_ACCESS=1 #1/0 31 | - APP_GUEST_ACCESS=1 #1/0 32 | - XMPP_ANON_HOST=anon.localhost 33 | - APP_IS_TRANSPORTS_USER_ALLOWED=1 #1/0 34 | - APP_HTTP_AUTODISCOVERY=0 #1/0 35 | - APP_RESOURCE=Web XMPP Docker latest 36 | - APP_DEFAULT_DOMAIN=localhost #mandatory 37 | - APP_DEFAULT_MUC=conference.localhost 38 | - APP_IS_STYLING_DISABLED=0 #1/0 39 | - APP_HAS_SENDING_ENTER_KEY=0 #1/0 40 | - XMPP_CONNECT_TIMEOUT=10000 41 | - APP_PINNED_MUCS=['welcome@conference.localhost'] 42 | - XMPP_WS=http://prosody:5280/xmpp-websocket #mandatory, use XMPP service name as hostname 43 | # volumes: 44 | # - ./certs:/etc/nginx/certs 45 | -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | set -e 3 | 4 | data_dir_owner="$(stat -c %u "/var/lib/prosody/")" 5 | if [[ "$(id -u prosody)" != "$data_dir_owner" ]]; then 6 | usermod -u "$data_dir_owner" prosody 7 | fi 8 | if [[ "$(stat -c %u /var/run/prosody/)" != "$data_dir_owner" ]]; then 9 | chown "$data_dir_owner" /var/run/prosody/ 10 | fi 11 | 12 | if [[ "$1" != "prosody" ]]; then 13 | exec prosodyctl "$@" 14 | exit 0; 15 | fi 16 | 17 | if [[ "$LOCAL" && "$PASSWORD" && "$DOMAIN" ]]; then 18 | prosodyctl register "$LOCAL" "$DOMAIN" "$PASSWORD" 19 | # update to register 2 users 20 | prosodyctl register "user1" "$DOMAIN" "user1" 21 | prosodyctl register "user2" "$DOMAIN" "user2" 22 | fi 23 | 24 | exec setpriv --reuid=prosody --regid=prosody --init-groups "$@" 25 | -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/modules/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /docs/staging-environments/prosody-std/prosody.cfg.lua: -------------------------------------------------------------------------------- 1 | daemonize = false; 2 | -- Prosody XMPP Server Configuration 3 | -- 4 | -- Information on configuring Prosody can be found on our 5 | -- website at https://prosody.im/doc/configure 6 | -- 7 | -- Tip: You can check that the syntax of this file is correct 8 | -- when you have finished by running this command: 9 | -- prosodyctl check config 10 | -- If there are any errors, it will let you know what and where 11 | -- they are, otherwise it will keep quiet. 12 | -- 13 | -- Good luck, and happy Jabbering! 14 | 15 | 16 | ---------- Server-wide settings ---------- 17 | -- Settings in this section apply to the whole server and are the default settings 18 | -- for any virtual hosts 19 | 20 | -- This is a (by default, empty) list of accounts that are admins 21 | -- for the server. Note that you must create the accounts separately 22 | -- (see https://prosody.im/doc/creating_accounts for info) 23 | -- Example: admins = { "user1@example.com", "user2@example.net" } 24 | admins = { "admin@localhost" } 25 | 26 | -- Enable use of libevent for better performance under high load 27 | -- For more information see: https://prosody.im/doc/libevent 28 | --use_libevent = true 29 | 30 | -- Prosody will always look in its source directory for modules, but 31 | -- this option allows you to specify additional locations where Prosody 32 | -- will look for modules first. For community modules, see https://modules.prosody.im/ 33 | plugin_paths = { "/usr/lib/prosody/custom-modules" } 34 | 35 | -- This is the list of modules Prosody will load on startup. 36 | -- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too. 37 | -- Documentation for bundled modules can be found at: https://prosody.im/doc/modules 38 | modules_enabled = { 39 | 40 | -- Generally required 41 | "roster"; -- Allow users to have a roster. Recommended ;) 42 | "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. 43 | -- "tls"; -- Add support for secure TLS on c2s/s2s connections 44 | "dialback"; -- s2s dialback support 45 | "disco"; -- Service discovery 46 | 47 | -- Not essential, but recommended 48 | "carbons"; -- Keep multiple clients in sync 49 | "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more 50 | "private"; -- Private XML storage (for room bookmarks, etc.) 51 | "blocklist"; -- Allow users to block communications with other users 52 | "vcard4"; -- User profiles (stored in PEP) 53 | "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard 54 | --"limits"; -- Enable bandwidth limiting for XMPP connections 55 | 56 | -- Nice to have 57 | "version"; -- Replies to server version requests 58 | "uptime"; -- Report how long server has been running 59 | "time"; -- Let others know the time here on this server 60 | "ping"; -- Replies to XMPP pings with pongs 61 | "register"; -- Allow users to register on this server using a client and change passwords 62 | "mam"; -- Store messages in an archive and allow users to access it 63 | --"csi_simple"; -- Simple Mobile optimizations 64 | 65 | -- Admin interfaces 66 | "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands 67 | --"admin_telnet"; -- Opens telnet console interface on localhost port 5582 68 | 69 | -- HTTP modules 70 | --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" 71 | "websocket"; -- XMPP over WebSockets 72 | --"http_files"; -- Serve static files from a directory over HTTP 73 | 74 | -- Other specific functionality 75 | --"groups"; -- Shared roster support 76 | --"server_contact_info"; -- Publish contact information for this service 77 | --"announce"; -- Send announcement to all online users 78 | --"welcome"; -- Welcome users who register accounts 79 | --"watchregistrations"; -- Alert admins of registrations 80 | --"motd"; -- Send a message to users when they log in 81 | --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. 82 | "proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use 83 | } 84 | 85 | -- These modules are auto-loaded, but should you want 86 | -- to disable them then uncomment them here: 87 | modules_disabled = { 88 | -- "offline"; -- Store offline messages 89 | -- "c2s"; -- Handle client connections 90 | "s2s"; -- Handle server-to-server connections 91 | -- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc. 92 | } 93 | 94 | -- Disable account creation by default, for security 95 | -- For more information see https://prosody.im/doc/creating_accounts 96 | allow_registration = false 97 | 98 | -- Force clients to use encrypted connections? This option will 99 | -- prevent clients from authenticating unless they are using encryption. 100 | 101 | c2s_require_encryption = false 102 | 103 | -- Force servers to use encrypted connections? This option will 104 | -- prevent servers from authenticating unless they are using encryption. 105 | 106 | s2s_require_encryption = false 107 | 108 | -- Force certificate authentication for server-to-server connections? 109 | 110 | s2s_secure_auth = false 111 | allow_unencrypted_plain_auth = true 112 | disable_sasl_mechanisms = {} 113 | 114 | -- Some servers have invalid or self-signed certificates. You can list 115 | -- remote domains here that will not be required to authenticate using 116 | -- certificates. They will be authenticated using DNS instead, even 117 | -- when s2s_secure_auth is enabled. 118 | 119 | --s2s_insecure_domains = { "insecure.example" } 120 | 121 | -- Even if you disable s2s_secure_auth, you can still require valid 122 | -- certificates for some domains by specifying a list here. 123 | 124 | --s2s_secure_domains = { "jabber.org" } 125 | 126 | -- Enable rate limits for incoming client and server connections 127 | 128 | -- limits = { 129 | -- c2s = { 130 | -- rate = "10kb/s"; 131 | -- }; 132 | -- s2sin = { 133 | -- rate = "30kb/s"; 134 | -- }; 135 | -- } 136 | 137 | -- Required for init scripts and prosodyctl 138 | -- pidfile = "/var/run/prosody/prosody.pid" 139 | 140 | -- Select the authentication backend to use. The 'internal' providers 141 | -- use Prosody's configured data storage to store the authentication data. 142 | 143 | authentication = "internal_hashed" 144 | 145 | -- Select the storage backend to use. By default Prosody uses flat files 146 | -- in its configured data directory, but it also supports more backends 147 | -- through modules. An "sql" backend is included by default, but requires 148 | -- additional dependencies. See https://prosody.im/doc/storage for more info. 149 | 150 | --storage = "sql" -- Default is "internal" 151 | 152 | -- For the "sql" backend, you can uncomment *one* of the below to configure: 153 | --sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. 154 | --sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } 155 | --sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } 156 | 157 | 158 | -- Archiving configuration 159 | -- If mod_mam is enabled, Prosody will store a copy of every message. This 160 | -- is used to synchronize conversations between multiple clients, even if 161 | -- they are offline. This setting controls how long Prosody will keep 162 | -- messages in the archive before removing them. 163 | 164 | archive_expires_after = "1w" -- Remove archived messages after 1 week 165 | 166 | -- You can also configure messages to be stored in-memory only. For more 167 | -- archiving options, see https://prosody.im/doc/modules/mod_mam 168 | 169 | -- Logging configuration 170 | -- For advanced logging see https://prosody.im/doc/logging 171 | log = { 172 | {levels = {min = "debug"}, to = "console"}; 173 | } 174 | 175 | -- Uncomment to enable statistics 176 | -- For more info see https://prosody.im/doc/statistics 177 | -- statistics = "internal" 178 | 179 | -- Certificates 180 | -- Every virtual host and component needs a certificate so that clients and 181 | -- servers can securely verify its identity. Prosody will automatically load 182 | -- certificates/keys from the directory specified here. 183 | -- For more information, including how to use 'prosodyctl' to auto-import certificates 184 | -- (from e.g. Let's Encrypt) see https://prosody.im/doc/certificates 185 | 186 | -- Location of directory to find certificates in (relative to main config file): 187 | certificates = "certs" 188 | 189 | -- HTTPS currently only supports a single certificate, specify it here: 190 | --https_certificate = "/etc/prosody/certs/localhost.crt" 191 | 192 | -- custom config for http_upload module 193 | http_upload_file_size_limit = 10485760 -- 10 Mb in bytes 194 | http_upload_expire_after = 60 * 60 * 24 * 1 -- a day in seconds 195 | http_upload_quota = 209715200 -- 200 Mb in bytes 196 | 197 | ----------- Virtual hosts ----------- 198 | -- You need to add a VirtualHost entry for each domain you wish Prosody to serve. 199 | -- Settings under each VirtualHost entry apply *only* to that host. 200 | consider_websocket_secure = true 201 | cross_domain_websocket = { "http://localhost", "https://localhost", "http://localhost:3000" } 202 | 203 | VirtualHost "localhost" 204 | name = "XMPP server" 205 | enabled = true 206 | 207 | VirtualHost "anon.localhost" 208 | authentication = "anonymous" 209 | disco_items = { 210 | { "upload.localhost", "File upload" }; 211 | } 212 | 213 | ------ Components ------ 214 | -- You can specify components to add hosts that provide special services, 215 | -- like multi-user conferences, and transports. 216 | -- For more information on components, see https://prosody.im/doc/components 217 | 218 | ---Set up a MUC (multi-user chat) room server on conference.localhost: 219 | Component "conference.localhost" "muc" 220 | modules_enabled = { 221 | "muc_mam"; 222 | "vcard_muc"; 223 | } 224 | name = "Conferences server" 225 | restrict_room_creation = "local" 226 | max_history_messages = 50 227 | 228 | ---Set up a proxy on proxy.localhost: 229 | Component "proxy.localhost" "proxy65" 230 | name = "SOCKS5 file transfert proxy service" 231 | proxy65_address = "localhost" 232 | proxy65_acl = { "localhost" } 233 | 234 | ---Set up a HTTP file upload on upload.localhost: 235 | Component "upload.localhost" "http_upload" 236 | 237 | ---Set up an external component (default component port is 5347) 238 | -- 239 | -- External components allow adding various services, such as gateways/ 240 | -- transports to other networks like ICQ, MSN and Yahoo. For more info 241 | -- see: https://prosody.im/doc/components#adding_an_external_component 242 | -- 243 | --Component "gateway.example.com" 244 | -- component_secret = "password" 245 | -------------------------------------------------------------------------------- /docs/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # config 6 | tempfolder=/var/tmp 7 | webfolder=/var/tmp/xmpp-web-html 8 | 9 | # check folder 10 | read -p "This will delete $webfolder/*, is this correct? (Y/N) " choice 11 | if [ "$choice" != "Y" ] 12 | then 13 | echo "Aborting..." 14 | exit 0 15 | fi 16 | 17 | # get latest version 18 | version=$(curl --silent https://api.github.com/repos/nioc/xmpp-web/releases/latest | jq --raw-output .tag_name) 19 | 20 | # download 21 | echo Downloading https://github.com/nioc/xmpp-web/releases/latest/download/xmpp-web-$version.tar.gz 22 | curl --location --silent https://github.com/nioc/xmpp-web/releases/latest/download/xmpp-web-$version.tar.gz --output $tempfolder/xmpp-web.tar.gz 23 | 24 | # unpack 25 | echo Unpacking $tempfolder/xmpp-web.tar.gz 26 | tar -xzf $tempfolder/xmpp-web.tar.gz --directory $tempfolder 27 | 28 | # backup local.js 29 | cp $webfolder/local.js $tempfolder/local.js.bk 30 | echo Previous config saved as $tempfolder/local.js.bak 31 | 32 | # move to web folder 33 | rm $webfolder/* -r 34 | mv -f $tempfolder/xmpp-web/* $webfolder/ 35 | 36 | # restore local.js.bak 37 | mv $tempfolder/local.js.bk $webfolder/local.js.bak 38 | echo Your config was saved as $webfolder/local.js.bak, you need to compare and edit default $webfolder/local.js 39 | 40 | # set owner 41 | chown www-data $webfolder/ -R 42 | 43 | # cleaning 44 | rm $tempfolder/xmpp-web -r 45 | rm $tempfolder/xmpp-web.tar.gz 46 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | XMPP web 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmpp-web", 3 | "version": "0.10.6", 4 | "private": true, 5 | "description": "Lightweight web chat client for XMPP server", 6 | "homepage": "https://github.com/nioc/xmpp-web", 7 | "bugs": { 8 | "url": "https://github.com/nioc/xmpp-web/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/nioc/xmpp-web.git" 13 | }, 14 | "author": "nioc ", 15 | "license": "AGPL-3.0-or-later", 16 | "scripts": { 17 | "dev": "vite --host 0.0.0.0 --https false", 18 | "dev-https": "vite --host 0.0.0.0 --https", 19 | "build": "vite build", 20 | "preview": "vite build && vite preview --port 8080", 21 | "configure-git-hook": "cp docs/git-hooks/commit-msg .git/hooks/", 22 | "cy:open-e2e": "cypress open --e2e --config baseUrl=http://localhost:3000/ --browser electron", 23 | "cy:run-e2e-dev": "cypress run --e2e --config baseUrl=http://localhost:3000/", 24 | "cy:run-e2e-preview": "cypress run --e2e --config baseUrl=http://localhost:8080/", 25 | "test:dev": "start-server-and-test dev http://localhost:3000 cy:run-e2e-dev", 26 | "test:preview": "start-server-and-test preview http://localhost:8080 cy:run-e2e-preview", 27 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-path .gitignore --ignore-pattern 'docs/*'", 28 | "lint-fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore --ignore-pattern 'docs/*'" 29 | }, 30 | "xmppWeb": { 31 | "latestReleaseUrl": "https://api.github.com/repos/nioc/xmpp-web/releases/latest" 32 | }, 33 | "dependencies": { 34 | "@creativebulma/bulma-divider": "^1.1.0", 35 | "@oruga-ui/oruga-next": "^0.5.10", 36 | "@oruga-ui/theme-bulma": "^0.2.11", 37 | "@vueuse/core": "^9.13.0", 38 | "@xmpp/client": "^0.13.4", 39 | "@xmpp/debug": "^0.13.3", 40 | "@xmpp/error": "^0.13.2", 41 | "axios": "^1.9.0", 42 | "bulma": "^0.9.4", 43 | "dayjs": "^1.11.11", 44 | "filesize": "^10.1.6", 45 | "fork-awesome": "^1.2.0", 46 | "gemoji": "^8.1.0", 47 | "mime": "^4.0.7", 48 | "nanoid": "^5.1.5", 49 | "pinia": "^2.3.1", 50 | "sanitize-html": "^2.17.0", 51 | "spdx-license-list": "^6.10.0", 52 | "vue": "^3.2.37", 53 | "vue-router": "^4.5.1" 54 | }, 55 | "devDependencies": { 56 | "@cypress/code-coverage": "^3.14.4", 57 | "@types/sanitize-html": "^2.16.0", 58 | "@vitejs/plugin-vue": "^4.6.2", 59 | "cypress": "^13.17.0", 60 | "eslint": "^8.57.1", 61 | "eslint-plugin-vue": "^9.33.0", 62 | "mock-socket": "^9.3.1", 63 | "prettier": "^2.8.8", 64 | "rollup-plugin-visualizer": "^6.0.1", 65 | "sass": "^1.89.1", 66 | "start-server-and-test": "^2.0.12", 67 | "vite": "^4.5.14", 68 | "vite-plugin-istanbul": "^5.0.0", 69 | "vite-plugin-mkcert": "^1.17.8", 70 | "vite-plugin-pwa": "^0.14.7" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nioc/xmpp-web/82aa89676f66b0d4a82524bf5eb4bbe5a05f1551/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/local.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars, no-var 2 | var config = { 3 | name: 'XMPP web', 4 | transports: { 5 | websocket: 'wss://chat.domain-web.ltd/xmpp-websocket', 6 | }, 7 | hasGuestAccess: true, 8 | hasRegisteredAccess: true, 9 | anonymousHost: null, 10 | // anonymousHost: 'anon.domain-xmpp.ltd', 11 | isTransportsUserAllowed: false, 12 | hasHttpAutoDiscovery: false, 13 | resource: 'Web XMPP', 14 | defaultDomain: 'domain-xmpp.ltd', 15 | defaultMuc: null, 16 | // defaultMuc: 'conference.domain-xmpp.ltd', 17 | isStylingDisabled: false, 18 | hasSendingEnterKey: false, 19 | connectTimeout: 5000, 20 | pinnedMucs: [], 21 | logoUrl: '', 22 | sso: { 23 | endpoint: false, 24 | jidHeader: 'jid', 25 | passwordHeader: 'password', 26 | }, 27 | guestDescription: '', 28 | } 29 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | -------------------------------------------------------------------------------- /src/assets/defaultAvatar.js: -------------------------------------------------------------------------------- 1 | export default 'data:image/svg+xml,%3Csvg width="334.26" height="334.26" enable-background="new 0 0 351.333 351.333" version="1.1" viewBox="0 0 334.26 334.26" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"%3E%3Crect width="334.26" height="334.26" rx="0" ry="0" fill="%23edeef2" style="paint-order:markers stroke fill"/%3E%3Cpath d="m100.08 147.42s-6.6224-89.335 52.581-89.102c0 0 10.703-6.0401 50.676 3.0283 0 0 43.286 11.448 33.836 85.726 0 0 3.604 7.3651 3.565 19.255-0.024 7.19-0.43831 21.357-11.744 30.151 0 0-2.5163 23.071-23.849 40.404 0 0-4.9206 23.395 2.667 30 34.161 22.063 104.19 17.426 104.95 67.379h-295.53c0.48993-50.872 87.364-50.653 108.01-69.05 0 0 6.0062-10.573 2.7826-28.53 0 0-20.492-23.204-20.638-41.944 0 0-11.264-8.356-11.887-23.424-0.25-6.058-1.0857-16.903 4.5823-23.894z" fill="%237e8dc8"/%3E%3C/svg%3E%0A' 2 | -------------------------------------------------------------------------------- /src/assets/styles.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import "/node_modules/bulma/sass/utilities/_all"; 4 | 5 | $primary: #2da192; 6 | $primary-light: lighten($primary, 10); 7 | $primary-invert: findColorInvert($primary); 8 | $warning-light: lighten($warning, 10); 9 | $shade-1: #3d444b; 10 | $shade-1-invert: findColorInvert($shade-1); 11 | $shade-2: #33393f; 12 | $shade-2-invert: findColorInvert($shade-2); 13 | $shade-3: #282e33; 14 | $shade-3-invert: findColorInvert($shade-3); 15 | $shade-4: #18191d; 16 | $shade-4-invert: findColorInvert($shade-4); 17 | 18 | $text: $shade-3-invert; 19 | $text-light: lighten($text, 10); 20 | $text-strong: darken($text, 5); 21 | $scheme-main: $shade-4; 22 | $scheme-main-bis: $shade-2; 23 | $link: $primary; 24 | $border-hover: $black-bis; 25 | $border: $black-ter; 26 | $label-color: $black-ter; 27 | $input-background-color: $shade-1; 28 | $code-background: $shade-3; 29 | $pre-background: $shade-2; 30 | $pre: $text; 31 | $code: $warning-light; 32 | $pre-padding: 1rem; 33 | code, 34 | pre { 35 | border-radius: 6px; 36 | } 37 | 38 | $link-focus: $black; 39 | $link-hover: $primary-light; 40 | 41 | $border-light: $black; 42 | $navbar-divider-background-color: $shade-3; 43 | $navbar-dropdown-item-hover-background-color: $shade-1; 44 | $navbar-dropdown-item-hover-color: $shade-1-invert; 45 | 46 | $dropdown-divider-background-color: $shade-3; 47 | $dropdown-item-hover-background-color: $shade-1; 48 | $dropdown-item-hover-color: $shade-1-invert; 49 | $dropdown-item-active-background-color: $shade-2; 50 | 51 | $link: $primary; 52 | $link-invert: $primary-invert; 53 | $link-focus-border: $primary; 54 | 55 | $menu-item-active-background-color: $primary; 56 | $menu-item-hover-background-color: $shade-1; 57 | 58 | $menu-list-border-left: 1px solid $shade-1; 59 | 60 | $menu-label-color: darken($text, 20); 61 | 62 | $file-cta-background-color: transparent; 63 | 64 | $colors: ( 65 | "white": ($white, $black), 66 | "black": ($black, $white), 67 | "light": ($light, $light-invert), 68 | "dark": ($dark, $dark-invert), 69 | "primary": ($primary, $primary-invert), 70 | "info": ($info, $info-invert), 71 | "success": ($success, $success-invert), 72 | "warning": ($warning, $warning-invert), 73 | "danger": ($danger, $danger-invert), 74 | "shade-1": ($shade-1, $shade-1-invert), 75 | "shade-2": ($shade-2, $shade-2-invert), 76 | "shade-3": ($shade-3, $shade-3-invert), 77 | "shade-4": ($shade-4, $shade-4-invert), 78 | ); 79 | 80 | $navbar-item-img-max-height: unset; 81 | 82 | $positions: ( 83 | "top", 84 | "left", 85 | "bottom", 86 | "right" 87 | ); 88 | 89 | @each $position in $positions { 90 | .has-border-#{$position}-shade-3 { 91 | border-#{$position}: 2px solid $shade-3 !important; 92 | } 93 | } 94 | 95 | $loading-background: rgba(24, 25, 29, 0.2); 96 | $loading-background-legacy:rgba(24, 25, 29, 0.2); 97 | 98 | @import "/node_modules/bulma/bulma.sass"; 99 | @import '/node_modules/@oruga-ui/theme-bulma/dist/scss/bulma'; 100 | @import "/node_modules/@creativebulma/bulma-divider"; 101 | 102 | html, 103 | body, 104 | .is-full-height { 105 | height: 100%; 106 | overflow-y: hidden; 107 | } 108 | 109 | .is-full-height-scrollable { 110 | height: 100%; 111 | overflow-y: auto; 112 | } 113 | 114 | .has-no-border { 115 | border: none; 116 | } 117 | .has-no-border:focus { 118 | border: none; 119 | } 120 | 121 | .has-placeholder-shade-1::placeholder { 122 | color: $shade-1; 123 | } 124 | 125 | .modal-close::before, 126 | .modal-close::after { 127 | background-color: $white; 128 | } 129 | 130 | .modal-card-head, .modal-card-foot { 131 | background-color: $shade-3; 132 | } 133 | 134 | .dialog .modal-card { 135 | max-width: 460px; 136 | min-width: 320px; 137 | width: auto; 138 | } 139 | 140 | .table td { 141 | vertical-align: middle; 142 | } 143 | 144 | .is-width-min-400 { 145 | min-width: 400px; 146 | } 147 | 148 | .is-primary-ghost { 149 | background-color: transparent; 150 | color: $primary; 151 | &:hover, &:focus { 152 | background-color: transparent; 153 | color:$primary-light; 154 | } 155 | } 156 | 157 | .is-warning-ghost { 158 | background-color: transparent; 159 | color: $warning; 160 | &:hover, &:focus { 161 | color:$warning-light; 162 | } 163 | } 164 | 165 | .menu-list li { 166 | margin: 2px 0; 167 | } 168 | 169 | .is-select-all { 170 | user-select: text; 171 | -webkit-user-select: text; 172 | -webkit-touch-callout: all; 173 | -webkit-user-select: all; 174 | -moz-user-select: all; 175 | -ms-user-select: text; 176 | user-select: all; 177 | } 178 | 179 | .has-no-wrap { 180 | white-space: nowrap; 181 | } 182 | 183 | .messages-container { 184 | overflow-y: auto; 185 | scroll-behavior: smooth; 186 | flex-grow: 1; 187 | } 188 | .toolbar { 189 | position: relative; 190 | padding: 0.5em 0.3em; 191 | height: 3em; 192 | display: flex; 193 | align-items: center; 194 | justify-content: space-between; 195 | } 196 | .message-text { 197 | white-space: pre-wrap; 198 | border-radius: 3px 9px 9px 9px !important; 199 | padding: 0.25em 0.75em; 200 | margin: 0 0.75em; 201 | } 202 | .messages-list-enter-active { 203 | transition: opacity 0.3s ease-out; 204 | } 205 | .messages-list-enter-from { 206 | opacity: 0; 207 | } 208 | .is-flex-direction-row-reverse .message-text { 209 | border-radius: 9px 3px 9px 9px !important; 210 | } 211 | .is-msg-moderated { 212 | opacity: 30%; 213 | } 214 | .sendbox textarea { 215 | resize: none; 216 | border: none; 217 | } 218 | .sendbox .thumbnail-container { 219 | position: absolute; 220 | top: 1em; 221 | left: 1em; 222 | } 223 | .sendbox .thumbnail { 224 | max-height: 2.5em; 225 | } 226 | .sendbox .delete { 227 | margin-left: -7px; 228 | margin-top: -13px; 229 | } 230 | 231 | .emojiPicker .b-tabs, 232 | .emojiPicker .tab-content { 233 | height: 100%; 234 | overflow-y: hidden; 235 | padding: 0; 236 | } 237 | .emojiPicker { 238 | position: absolute; 239 | left: 0; 240 | right: 0; 241 | top: 0; 242 | bottom: 0; 243 | z-index: 1; 244 | background-color: $shade-4; 245 | } 246 | .emoji { 247 | width: 2.5rem; 248 | text-align: center; 249 | } 250 | -------------------------------------------------------------------------------- /src/components/About.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 77 | -------------------------------------------------------------------------------- /src/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 87 | 88 | 100 | -------------------------------------------------------------------------------- /src/components/BookmarkButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | -------------------------------------------------------------------------------- /src/components/Chat.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 247 | -------------------------------------------------------------------------------- /src/components/Contact.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 69 | 70 | 85 | -------------------------------------------------------------------------------- /src/components/Contacts.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 106 | 107 | 112 | -------------------------------------------------------------------------------- /src/components/EmojiPicker.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 61 | -------------------------------------------------------------------------------- /src/components/Group.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 44 | -------------------------------------------------------------------------------- /src/components/GuestChat.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/components/GuestHome.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 110 | -------------------------------------------------------------------------------- /src/components/GuestRooms.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 187 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 76 | -------------------------------------------------------------------------------- /src/components/InviteGuestButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | -------------------------------------------------------------------------------- /src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 164 | -------------------------------------------------------------------------------- /src/components/Message.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 134 | -------------------------------------------------------------------------------- /src/components/MessageLink.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 73 | 74 | 84 | -------------------------------------------------------------------------------- /src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 117 | -------------------------------------------------------------------------------- /src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 72 | -------------------------------------------------------------------------------- /src/components/NotificationsSwitch.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | -------------------------------------------------------------------------------- /src/components/Presence.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 67 | -------------------------------------------------------------------------------- /src/components/PresenceController.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 117 | -------------------------------------------------------------------------------- /src/components/Profile.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 86 | -------------------------------------------------------------------------------- /src/components/RetrieveHistoryButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/components/RoomConfiguration.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 91 | -------------------------------------------------------------------------------- /src/components/RoomConfigurationButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /src/components/RoomCreation.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 74 | -------------------------------------------------------------------------------- /src/components/RoomOccupants.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | -------------------------------------------------------------------------------- /src/components/RoomSubject.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 46 | -------------------------------------------------------------------------------- /src/components/RoomsList.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 70 | -------------------------------------------------------------------------------- /src/components/Sendbox.vue: -------------------------------------------------------------------------------- 1 |