snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,657 @@
# @rocket.chat/omni-core
## 0.1.4
### Patch Changes
- <details><summary>Updated dependencies [4947601bbf042cd1b2385f8f5dda438e608faea7, b89a8d411ef65f6931a5fd1cd057740bc00cd9ba, 5deefe2766132f13118f38dc42cf1c9895208961, 8984df841e1ecc46198e073982adabc1f069c352, a9f3e6cd3f31eac17bec626287fcf815d802e9b9, 742009a09148e33d141f50691cdec0f7e9818535]:</summary>
- @rocket.chat/models@2.5.0
</details>
## 0.1.4-rc.0
### Patch Changes
- <details><summary>Updated dependencies [4947601bbf042cd1b2385f8f5dda438e608faea7, b89a8d411ef65f6931a5fd1cd057740bc00cd9ba, 5deefe2766132f13118f38dc42cf1c9895208961, 8984df841e1ecc46198e073982adabc1f069c352, a9f3e6cd3f31eac17bec626287fcf815d802e9b9, 742009a09148e33d141f50691cdec0f7e9818535]:</summary>
- @rocket.chat/models@2.5.0-rc.0
- @rocket.chat/patch-injection@0.0.2
</details>
## 0.1.3
### Patch Changes
- <details><summary>Updated dependencies [c7aff48a40a9a78924cbf27fd38930c536ee11e5, 5f92f9a27dca70d506d919351612bd32dc04241a, 13b4a7b2dc203959b77b3b0c5f154d3e34fe2058, 4b34bd62f2ac8d51efd2f48caea7092e87f30ce7, ffe1b646226eeeda5a4d4697c831e568ec1eec64, 3cd7db677a72521439b564dca7a4ca6d6c3a1c07, 719e3db9734708e812ceb33483ffaa2e064b4d59, 1cc7bbdef9330899a8207d3d55130f48321d68f1]:</summary>
- @rocket.chat/models@2.4.0
</details>
## 0.1.3-rc.0
### Patch Changes
- <details><summary>Updated dependencies [c7aff48a40a9a78924cbf27fd38930c536ee11e5, 5f92f9a27dca70d506d919351612bd32dc04241a, 13b4a7b2dc203959b77b3b0c5f154d3e34fe2058, 4b34bd62f2ac8d51efd2f48caea7092e87f30ce7, ffe1b646226eeeda5a4d4697c831e568ec1eec64, 3cd7db677a72521439b564dca7a4ca6d6c3a1c07, 719e3db9734708e812ceb33483ffaa2e064b4d59, 1cc7bbdef9330899a8207d3d55130f48321d68f1]:</summary>
- @rocket.chat/models@2.4.0-rc.0
</details>
## 0.1.2
### Patch Changes
- <details><summary>Updated dependencies [73e12e1707baea845395e0582892f65456598672, 7380c44c751eff9ee624d80bf26370411ffed78b]:</summary>
- @rocket.chat/models@2.3.1
</details>
## 0.1.2-rc.0
### Patch Changes
- <details><summary>Updated dependencies [73e12e1707baea845395e0582892f65456598672, 7380c44c751eff9ee624d80bf26370411ffed78b]:</summary>
- @rocket.chat/models@2.3.1-rc.0
</details>
## 0.1.1
### Patch Changes
- <details><summary>Updated dependencies [f7d47dd3517ec14ca2ec5c3c95fcdf9e1e2fb8b0, b6b04aadfcc8558f888b334e37c46a77e5816237, 4704bf81ca370f120af32185a7c55407a26f8514, 12897e25d0dc25b7373f5264d38f38a5a7444257, e45585b70a3a7b75434c88e4b2ea9af0a0764a76]:</summary>
- @rocket.chat/models@2.3.0
</details>
## 0.1.1-rc.0
### Patch Changes
- <details><summary>Updated dependencies [f7d47dd3517ec14ca2ec5c3c95fcdf9e1e2fb8b0, ae9f740d6af20557eac61b4af902c868b4132b49, b6b04aadfcc8558f888b334e37c46a77e5816237, 4704bf81ca370f120af32185a7c55407a26f8514, 12897e25d0dc25b7373f5264d38f38a5a7444257, e45585b70a3a7b75434c88e4b2ea9af0a0764a76]:</summary>
- @rocket.chat/models@2.3.0-rc.0
</details>
## 0.1.0
### Minor Changes
- ([#39535](https://github.com/RocketChat/Rocket.Chat/pull/39535)) Adds externalIds field to livechat visitors for external platform identification.
### Patch Changes
- ([#40175](https://github.com/RocketChat/Rocket.Chat/pull/40175)) Fixes an issue where the Omnichannel routing system ignored the `Livechat_accept_chats_with_no_agents` setting. Now, offline agents are correctly considered for assignment when the setting allows it.
- <details><summary>Updated dependencies [21cd54f87de5837fe6c999a44bd15be34b9fe905, 278b84f78360e53792a2e5d7620615039a0e15e9, 12c44d2db65af9f90c741da621164f5738fc19f5, 9713af36f5c0d673f2d2093015f322341706bab0, f3fa3ee2f2e53b777de9abd466f1b76a1ec1b96c, f4dfb8ddc2049692371aeb084110b5768151b5df]:</summary>
- @rocket.chat/models@2.2.0
</details>
## 0.1.0-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.2.0-rc.2
</details>
## 0.1.0-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.2.0-rc.1
</details>
## 0.0.20
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.4
</details>
## 0.0.19
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.3
</details>
## 0.1.0-rc.0
### Minor Changes
- ([#39535](https://github.com/RocketChat/Rocket.Chat/pull/39535)) Adds externalIds field to livechat visitors for external platform identification.
### Patch Changes
- ([#40175](https://github.com/RocketChat/Rocket.Chat/pull/40175)) Fixes an issue where the Omnichannel routing system ignored the `Livechat_accept_chats_with_no_agents` setting. Now, offline agents are correctly considered for assignment when the setting allows it.
- <details><summary>Updated dependencies [21cd54f87de5837fe6c999a44bd15be34b9fe905, 278b84f78360e53792a2e5d7620615039a0e15e9, 12c44d2db65af9f90c741da621164f5738fc19f5, 9713af36f5c0d673f2d2093015f322341706bab0, f3fa3ee2f2e53b777de9abd466f1b76a1ec1b96c, f4dfb8ddc2049692371aeb084110b5768151b5df]:</summary>
- @rocket.chat/models@2.2.0-rc.0
</details>
## 0.0.18
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
- <details><summary>Updated dependencies [602b20a8c570b895eb296ecfe39c9b7fcb12fabd, e2068892bf1ffc88b15ab71ad743cf84e5d31ed5, cd2fc208d351032c0b729755af4886665dca08b6, 539659af22bc19880eda047dfc0b152472ccb65c, eae3fb3136bd0b48294c050a71b0a36d05ca02b0]:</summary>
- @rocket.chat/models@2.1.2
- @rocket.chat/patch-injection@0.0.2
</details>
## 0.0.18-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.2-rc.4
</details>
## 0.0.18-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.2-rc.3
</details>
## 0.0.18-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.2-rc.2
</details>
## 0.0.18-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.2-rc.1
</details>
## 0.0.18-rc.0
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
- <details><summary>Updated dependencies [602b20a8c570b895eb296ecfe39c9b7fcb12fabd, e2068892bf1ffc88b15ab71ad743cf84e5d31ed5, cd2fc208d351032c0b729755af4886665dca08b6, 539659af22bc19880eda047dfc0b152472ccb65c, eae3fb3136bd0b48294c050a71b0a36d05ca02b0]:</summary>
- @rocket.chat/models@2.1.2-rc.0
- @rocket.chat/patch-injection@0.0.2-rc.0
</details>
## 0.0.17
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.1
</details>
## 0.0.16
### Patch Changes
- <details><summary>Updated dependencies [d3758a7d57ab602745369ef9d2ccdbf9271cf305, e57f15845e4df048dd2f08f11aa08215780a2c34, 3b003e6b69c11b280d55bcc8db2f3e4ae7a4a573, 508b4a17d76dc1cd7d3a55bdba826216f51432e2, 123aebec2caa74b17d2b5dcbd2a2db2e687cf3ac]:</summary>
- @rocket.chat/models@2.1.0
</details>
## 0.0.16-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.0-rc.2
</details>
## 0.0.16-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.1.0-rc.1
</details>
## 0.0.16-rc.0
### Patch Changes
- <details><summary>Updated dependencies [d3758a7d57ab602745369ef9d2ccdbf9271cf305, e57f15845e4df048dd2f08f11aa08215780a2c34, 3b003e6b69c11b280d55bcc8db2f3e4ae7a4a573, 508b4a17d76dc1cd7d3a55bdba826216f51432e2, 123aebec2caa74b17d2b5dcbd2a2db2e687cf3ac]:</summary>
- @rocket.chat/models@2.1.0-rc.0
</details>
## 0.0.15
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.3
</details>
## 0.0.14
### Patch Changes
- <details><summary>Updated dependencies [f4fa393fccb6abefbe6fb6550563e8ca21fc792d, 020dfbcab6a940ca2e03d802d0f6b54714a34aa2, 5fa150953b86ff36face25083ed49e8c97a8044d, 05c415b94cb91907de39a39c6d277579258f334e]:</summary>
- @rocket.chat/models@2.0.2
</details>
## 0.0.14-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.2-rc.2
</details>
## 0.0.14-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.1-rc.1
</details>
## 0.0.14-rc.0
### Patch Changes
- <details><summary>Updated dependencies [f4fa393fccb6abefbe6fb6550563e8ca21fc792d, 020dfbcab6a940ca2e03d802d0f6b54714a34aa2, 5fa150953b86ff36face25083ed49e8c97a8044d, 05c415b94cb91907de39a39c6d277579258f334e]:</summary>
- @rocket.chat/models@2.0.1-rc.0
</details>
## 0.0.13
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.1
</details>
## 0.0.12
### Patch Changes
- <details><summary>Updated dependencies [ac11ea05ffadeca978c794ff38d5199d9acb2c29, ac11ea05ffadeca978c794ff38d5199d9acb2c29, ddc935727e9a7275813006d9dcaa7fe866610844, 73d9eb2783176954f42aa2cbeda8abf1d49ac260, be80b724a636877294b5e5baa501d070941131dd]:</summary>
- @rocket.chat/models@2.0.0
</details>
## 0.0.12-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.0-rc.5
</details>
## 0.0.12-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.0-rc.4
</details>
## 0.0.12-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.0-rc.3
</details>
## 0.0.12-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.0-rc.2
</details>
## 0.0.12-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@2.0.0-rc.1
</details>
## 0.0.12-rc.0
### Patch Changes
- <details><summary>Updated dependencies [ac11ea05ffadeca978c794ff38d5199d9acb2c29, ac11ea05ffadeca978c794ff38d5199d9acb2c29, ddc935727e9a7275813006d9dcaa7fe866610844, 73d9eb2783176954f42aa2cbeda8abf1d49ac260, be80b724a636877294b5e5baa501d070941131dd]:</summary>
- @rocket.chat/models@2.0.0-rc.0
</details>
## 0.0.11
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.8.2
</details>
## 0.0.10
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.8.1
</details>
## 0.0.9
### Patch Changes
- <details><summary>Updated dependencies [83642cbe499ba399329449fb8cd652a0405c0795, 7f1b834a55b1240c226afde77713262da47f45dc]:</summary>
- @rocket.chat/models@1.8.0
</details>
## 0.0.9-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.8.0-rc.2
</details>
## 0.0.9-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.8.0-rc.1
</details>
## 0.0.9-rc.0
### Patch Changes
- <details><summary>Updated dependencies [83642cbe499ba399329449fb8cd652a0405c0795, 7f1b834a55b1240c226afde77713262da47f45dc]:</summary>
- @rocket.chat/models@1.8.0-rc.0
</details>
## 0.0.8
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.3
</details>
## 0.0.7
### Patch Changes
- <details><summary>Updated dependencies [8cbd9bfd0566cbd7d86d8b40ea7d58d6ef382742]:</summary>
- @rocket.chat/models@1.7.2
</details>
## 0.0.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1
</details>
## 0.0.6-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1-rc.4
</details>
## 0.0.6-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1-rc.3
</details>
## 0.0.6-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1-rc.2
</details>
## 0.0.6-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1-rc.1
</details>
## 0.0.6-rc.0
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.1-rc.0
</details>
## 0.0.5
### Patch Changes
- <details><summary>Updated dependencies [c102712222cc18c050f45f9279f8eafcff3e7e8a, 3e177dbd0b65d70bce8587287ba73b30170eb0f7]:</summary>
- @rocket.chat/models@1.7.0
</details>
## 0.0.5-rc.7
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.7
</details>
## 0.0.5-rc.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.6
</details>
## 0.0.4-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.5
</details>
## 0.0.4-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.4
</details>
## 0.0.4-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.3
</details>
## 0.0.4-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.2
</details>
## 0.0.4-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.7.0-rc.1
</details>
## 0.0.4-rc.0
### Patch Changes
- <details><summary>Updated dependencies [c102712222cc18c050f45f9279f8eafcff3e7e8a, 3e177dbd0b65d70bce8587287ba73b30170eb0f7]:</summary>
- @rocket.chat/models@1.7.0-rc.0
</details>
## 0.0.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.2
</details>
## 0.0.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.1
</details>
## 0.0.2
### Patch Changes
- <details><summary>Updated dependencies [b25f05acd07762387fa45d67a1241b982c192f5d, fd32867fd4949bc2951a22075498ccb551cc6bbc, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]:</summary>
- @rocket.chat/models@1.6.0
</details>
## 0.0.2-rc.6
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.6
</details>
## 0.0.2-rc.5
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.5
</details>
## 0.0.2-rc.4
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.4
</details>
## 0.0.2-rc.3
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.3
</details>
## 0.0.2-rc.2
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.2
</details>
## 0.0.2-rc.1
### Patch Changes
- <details><summary>Updated dependencies []:</summary>
- @rocket.chat/models@1.6.0-rc.1
</details>
## 0.0.2-rc.0
### Patch Changes
- <details><summary>Updated dependencies [b25f05acd07762387fa45d67a1241b982c192f5d, fd32867fd4949bc2951a22075498ccb551cc6bbc, c7db598e9f3c2ad47f6a6be2a9ba7078533c245b]:</summary>
- @rocket.chat/models@1.6.0-rc.0
</details>

View File

@@ -0,0 +1,6 @@
import server from '@rocket.chat/jest-presets/server';
import type { Config } from 'jest';
export default {
preset: server.preset,
} satisfies Config;

View File

@@ -0,0 +1,36 @@
{
"name": "@rocket.chat/omni-core",
"version": "0.1.4",
"private": true,
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
"/dist"
],
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.json",
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": "jest",
"testunit": "jest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@rocket.chat/models": "workspace:^",
"@rocket.chat/patch-injection": "workspace:^",
"mongodb": "6.16.0"
},
"devDependencies": {
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/tools": "workspace:*",
"@rocket.chat/tsconfig": "workspace:*",
"@types/jest": "~30.0.0",
"eslint": "~9.39.5",
"jest": "~30.2.0",
"typescript": "~5.9.3"
},
"volta": {
"extends": "../../package.json"
}
}

View File

@@ -0,0 +1,7 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import { makeFunction } from '@rocket.chat/patch-injection';
import type { FilterOperators } from 'mongodb';
export const applyDepartmentRestrictions = makeFunction(async (query: FilterOperators<ILivechatDepartment> = {}, _userId: string) => {
return query;
});

View File

@@ -0,0 +1,3 @@
export * from './isDepartmentCreationAvailable';
export * from './hooks/applyDepartmentRestrictions';
export * from './visitor/create';

View File

@@ -0,0 +1,20 @@
import type { ILivechatDepartmentModel } from '@rocket.chat/model-typings';
import { registerModel } from '@rocket.chat/models';
import { isDepartmentCreationAvailable } from './isDepartmentCreationAvailable';
it('should be available if there are no existing departments', async () => {
registerModel('ILivechatDepartmentModel', { countTotal: () => 0 } as unknown as ILivechatDepartmentModel);
const isAvailable = await isDepartmentCreationAvailable();
expect(isAvailable).toBe(true);
});
it('should not be available if there are existing departments', async () => {
registerModel('ILivechatDepartmentModel', { countTotal: () => 1 } as unknown as ILivechatDepartmentModel);
const isAvailable = await isDepartmentCreationAvailable();
expect(isAvailable).toBe(false);
});

View File

@@ -0,0 +1,7 @@
import { LivechatDepartment } from '@rocket.chat/models';
import { makeFunction } from '@rocket.chat/patch-injection';
export const isDepartmentCreationAvailable = makeFunction(async (): Promise<boolean> => {
// Only one department can exist at a time
return (await LivechatDepartment.countTotal()) === 0;
});

View File

@@ -0,0 +1,779 @@
import { UserStatus } from '@rocket.chat/core-typings';
import type { ILivechatContactsModel, ILivechatDepartmentModel, ILivechatVisitorsModel, IUsersModel } from '@rocket.chat/model-typings';
import { registerModel } from '@rocket.chat/models';
import { validateEmail } from '@rocket.chat/tools';
import { registerGuest } from './create';
// Mock the validateEmail function
jest.mock('@rocket.chat/tools', () => ({
validateEmail: jest.fn(),
}));
// Mock the Logger
jest.mock('@rocket.chat/logger', () => ({
Logger: jest.fn().mockImplementation(() => ({
debug: jest.fn(),
})),
}));
const mockValidateEmail = validateEmail as jest.MockedFunction<typeof validateEmail>;
describe('registerGuest', () => {
let updateOneByIdOrTokenSpy: jest.Mock;
let getVisitorByTokenSpy: jest.Mock;
let findOneVisitorByPhoneSpy: jest.Mock;
let findOneGuestByEmailAddressSpy: jest.Mock;
let getNextVisitorUsernameSpy: jest.Mock;
let findContactByEmailAndContactManagerSpy: jest.Mock;
let findOneOnlineAgentByIdSpy: jest.Mock;
let findOneByIdOrNameSpy: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockValidateEmail.mockImplementation(() => true);
// Create spies that return reasonable defaults
updateOneByIdOrTokenSpy = jest.fn().mockResolvedValue({ _id: 'visitor-123', token: 'test-token' });
getVisitorByTokenSpy = jest.fn().mockResolvedValue(null);
findOneVisitorByPhoneSpy = jest.fn().mockResolvedValue(null);
findOneGuestByEmailAddressSpy = jest.fn().mockResolvedValue(null);
getNextVisitorUsernameSpy = jest.fn().mockResolvedValue('guest-123');
findContactByEmailAndContactManagerSpy = jest.fn().mockResolvedValue(null);
findOneOnlineAgentByIdSpy = jest.fn().mockResolvedValue(null);
findOneByIdOrNameSpy = jest.fn().mockResolvedValue(null);
// Register the models with spy functions
registerModel('ILivechatVisitorsModel', {
getVisitorByToken: getVisitorByTokenSpy,
findOneVisitorByPhone: findOneVisitorByPhoneSpy,
findOneGuestByEmailAddress: findOneGuestByEmailAddressSpy,
getNextVisitorUsername: getNextVisitorUsernameSpy,
updateOneByIdOrToken: updateOneByIdOrTokenSpy,
} as unknown as ILivechatVisitorsModel);
registerModel('ILivechatContactsModel', {
findContactByEmailAndContactManager: findContactByEmailAndContactManagerSpy,
} as unknown as ILivechatContactsModel);
registerModel('IUsersModel', {
findOneOnlineAgentById: findOneOnlineAgentByIdSpy,
} as unknown as IUsersModel);
registerModel('ILivechatDepartmentModel', {
findOneByIdOrName: findOneByIdOrNameSpy,
} as unknown as ILivechatDepartmentModel);
});
describe('validation', () => {
it('should throw error when token is not provided', async () => {
const guestData = {};
await expect(registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false })).rejects.toThrow(
'error-invalid-token',
);
});
it('should throw error when token is empty string', async () => {
const guestData = {
token: '',
};
await expect(registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false })).rejects.toThrow(
'error-invalid-token',
);
});
});
describe('email validation and contact manager assignment', () => {
it('should validate email and assign contact manager when available', async () => {
const email = 'test@example.com';
const token = 'test-token';
const agentId = 'agent-123';
const mockAgent = {
_id: agentId,
username: 'agent.user',
name: 'Agent User',
emails: [{ address: 'agent@example.com' }],
};
const mockContact = {
contactManager: agentId,
};
findContactByEmailAndContactManagerSpy.mockResolvedValue(mockContact);
findOneOnlineAgentByIdSpy.mockResolvedValue(mockAgent);
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(mockValidateEmail).toHaveBeenCalledWith('test@example.com');
expect(findContactByEmailAndContactManagerSpy).toHaveBeenCalledWith('test@example.com');
expect(findOneOnlineAgentByIdSpy).toHaveBeenCalledWith(agentId, false, false, {
projection: { _id: 1, username: 1, name: 1, emails: 1 },
});
// Verify the data passed to updateOneByIdOrToken
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status: UserStatus.ONLINE,
visitorEmails: [{ address: email }],
contactManager: {
_id: agentId,
username: 'agent.user',
name: 'Agent User',
emails: [{ address: 'agent@example.com' }],
},
username: 'guest-123',
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should not assign contact manager when agent is not found', async () => {
const email = 'test@example.com';
const token = 'test-token';
const mockContact = {
contactManager: 'agent-123',
};
findContactByEmailAndContactManagerSpy.mockResolvedValue(mockContact);
findOneOnlineAgentByIdSpy.mockResolvedValue(null);
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
// Verify contact manager is not included in the data
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status: UserStatus.ONLINE,
visitorEmails: [{ address: email }],
username: 'guest-123',
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
// Ensure contactManager is not present
const callArgs = updateOneByIdOrTokenSpy.mock.calls[0][0];
expect(callArgs.contactManager).toBeUndefined();
});
it('should trim and lowercase email', async () => {
const email = ' TEST@EXAMPLE.COM ';
const token = 'test-token';
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(mockValidateEmail).toHaveBeenCalledWith('test@example.com');
expect(findContactByEmailAndContactManagerSpy).toHaveBeenCalledWith('test@example.com');
// Verify the trimmed and lowercase email is passed to updateOneByIdOrToken
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
visitorEmails: [{ address: 'test@example.com' }],
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
describe('department validation and assignment', () => {
it('should assign valid department', async () => {
const token = 'test-token';
const department = 'sales';
const departmentId = 'dept-123';
const mockDepartment = {
_id: departmentId,
};
findOneByIdOrNameSpy.mockResolvedValue(mockDepartment);
const guestData = {
token,
department,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(findOneByIdOrNameSpy).toHaveBeenCalledWith(department, { projection: { _id: 1 } });
// Verify the department ID is passed to updateOneByIdOrToken
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status: UserStatus.ONLINE,
department: departmentId,
username: 'guest-123',
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should throw error for invalid department', async () => {
const token = 'test-token';
const department = 'invalid-dept';
findOneByIdOrNameSpy.mockResolvedValue(null);
getVisitorByTokenSpy.mockResolvedValue({ department: 'different-dept' });
const guestData = {
token,
department,
};
await expect(registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false })).rejects.toThrow(
'error-invalid-department',
);
// Verify updateOneByIdOrToken is not called when department validation fails
expect(updateOneByIdOrTokenSpy).not.toHaveBeenCalled();
});
it('should not validate department if visitor already has the same department', async () => {
const token = 'test-token';
const department = 'sales';
getVisitorByTokenSpy.mockResolvedValue({
_id: 'visitor-123',
department,
token,
});
const guestData = {
token,
department,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
// Department validation should be skipped
expect(findOneByIdOrNameSpy).not.toHaveBeenCalled();
// Verify existing visitor is updated without department validation
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
_id: 'visitor-123',
token,
status: UserStatus.ONLINE,
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
describe('visitor matching and creation', () => {
it('should update existing visitor found by token', async () => {
const token = 'test-token';
const existingVisitor = {
_id: 'visitor-123',
token,
};
getVisitorByTokenSpy.mockResolvedValue(existingVisitor);
const guestData = {
token,
name: 'Updated Name',
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(getVisitorByTokenSpy).toHaveBeenCalledWith(token, { projection: { _id: 1, department: 1, token: 1 } });
// Verify existing visitor data is used and updated
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
_id: 'visitor-123',
token,
status: UserStatus.ONLINE,
name: 'Updated Name',
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should match visitor by phone number and preserve existing token', async () => {
const token = 'new-token';
const existingToken = 'existing-token';
const phoneNumber = '+1234567890';
const existingVisitor = {
_id: 'visitor-123',
token: existingToken,
};
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(existingVisitor);
const guestData = {
token,
phone: { number: phoneNumber },
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(findOneVisitorByPhoneSpy).toHaveBeenCalledWith(phoneNumber);
// Verify existing visitor's token is preserved, not the new one
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
_id: 'visitor-123',
token: existingToken, // Should use existing token, not new one
status: UserStatus.ONLINE,
phone: [{ phoneNumber }],
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should match visitor by email', async () => {
const token = 'test-token';
const email = 'test@example.com';
const existingVisitor = {
_id: 'visitor-123',
token: 'existing-token',
};
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(existingVisitor);
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(findOneGuestByEmailAddressSpy).toHaveBeenCalledWith(email);
// Verify existing visitor data is used
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
_id: 'visitor-123',
token,
status: UserStatus.ONLINE,
visitorEmails: [{ address: email }],
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should create new visitor when no matches found', async () => {
const token = 'test-token';
const username = 'custom-username';
const id = 'custom-id';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
id,
token,
username,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
// Verify new visitor data is created with provided values
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
_id: id,
token,
username,
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should generate username when not provided for new visitor', async () => {
const token = 'test-token';
const generatedUsername = 'guest-123';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(getNextVisitorUsernameSpy).toHaveBeenCalled();
// Verify generated username is used
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
username: generatedUsername,
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should use provided username for new visitor', async () => {
const token = 'test-token';
const providedUsername = 'custom-username';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
username: providedUsername,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(getNextVisitorUsernameSpy).not.toHaveBeenCalled();
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
username: providedUsername,
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
describe('data formatting', () => {
it('should format phone number correctly', async () => {
const token = 'test-token';
const phoneNumber = '+1234567890';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
phone: { number: phoneNumber },
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
phone: [{ phoneNumber }],
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should format email correctly', async () => {
const token = 'test-token';
const email = 'test@example.com';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
findContactByEmailAndContactManagerSpy.mockResolvedValue(null);
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
visitorEmails: [{ address: email }],
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should use default status when not provided', async () => {
const token = 'test-token';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should use custom status when provided', async () => {
const token = 'test-token';
const status = UserStatus.AWAY;
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
status,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
describe('connection data handling', () => {
it('should save connection data for new visitor', async () => {
const token = 'test-token';
const connectionData = {
httpHeaders: {
'user-agent': 'Mozilla/5.0',
'x-real-ip': '192.168.1.1',
'host': 'example.com',
},
clientAddress: '10.0.0.1',
};
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
connectionData,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
userAgent: 'Mozilla/5.0',
ip: '192.168.1.1',
host: 'example.com',
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should use x-forwarded-for header when x-real-ip is not available', async () => {
const token = 'test-token';
const connectionData = {
httpHeaders: {
'x-forwarded-for': '203.0.113.1',
},
clientAddress: '10.0.0.1',
};
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
connectionData,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
ip: '203.0.113.1',
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should use clientAddress when no IP headers are available', async () => {
const token = 'test-token';
const connectionData = {
httpHeaders: {},
clientAddress: '10.0.0.1',
};
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
const guestData = {
token,
connectionData,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
ip: '10.0.0.1',
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
describe('error scenarios', () => {
it('should return null when upsert fails', async () => {
const token = 'test-token';
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
// Mock upsert to return null (failure case)
updateOneByIdOrTokenSpy.mockResolvedValue(null);
const guestData = {
token,
};
const result = await registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false });
expect(result).toBeNull();
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
it('should throw error when email validation fails', async () => {
const token = 'test-token';
const email = 'invalid-email';
mockValidateEmail.mockImplementation(() => {
throw new Error('Invalid email');
});
const guestData = {
token,
email,
};
await expect(registerGuest(guestData, { shouldConsiderIdleAgent: false, shouldConsiderOfflineAgent: false })).rejects.toThrow(
'Invalid email',
);
});
});
describe('shouldConsiderIdleAgent parameter', () => {
it('should pass shouldConsiderIdleAgent and shouldConsiderOfflineAgent to findOneOnlineAgentById', async () => {
const token = 'test-token';
const email = 'test@example.com';
const agentId = 'agent-123';
const mockAgent = {
_id: agentId,
username: 'agent.user',
name: 'Agent User',
emails: [{ address: 'agent@example.com' }],
};
const mockContact = {
contactManager: agentId,
};
// All lookup methods return null (no matches)
getVisitorByTokenSpy.mockResolvedValue(null);
findOneVisitorByPhoneSpy.mockResolvedValue(null);
findOneGuestByEmailAddressSpy.mockResolvedValue(null);
findContactByEmailAndContactManagerSpy.mockResolvedValue(mockContact);
findOneOnlineAgentByIdSpy.mockResolvedValue(mockAgent);
const guestData = {
token,
email,
};
await registerGuest(guestData, { shouldConsiderIdleAgent: true, shouldConsiderOfflineAgent: true });
expect(findOneOnlineAgentByIdSpy).toHaveBeenCalledWith(
agentId,
true, // shouldConsiderIdleAgent should be true
true, // shouldConsiderOfflineAgent should be true
{ projection: { _id: 1, username: 1, name: 1, emails: 1 } },
);
expect(updateOneByIdOrTokenSpy).toHaveBeenCalledWith(
expect.objectContaining({
token,
visitorEmails: [{ address: email }],
contactManager: expect.objectContaining({
username: 'agent.user',
}),
status: UserStatus.ONLINE,
ts: expect.any(Date),
}),
{ upsert: true, returnDocument: 'after' },
);
});
});
});

View File

@@ -0,0 +1,118 @@
import { type ILivechatVisitor, type IVisitorExternalIdentifier, UserStatus } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { LivechatContacts, LivechatDepartment, LivechatVisitors, Users } from '@rocket.chat/models';
import { makeFunction } from '@rocket.chat/patch-injection';
import { validateEmail } from '@rocket.chat/tools';
const logger = new Logger('Livechat - Visitor');
type RegisterGuestType = Partial<Pick<ILivechatVisitor, 'token' | 'name' | 'department' | 'status' | 'username'>> & {
id?: string;
connectionData?: any;
email?: string;
phone?: { number: string };
externalIds?: IVisitorExternalIdentifier[];
};
export const registerGuest = makeFunction(
async (
{ id, token, name, phone, email, department, username, connectionData, status = UserStatus.ONLINE, externalIds }: RegisterGuestType,
{ shouldConsiderIdleAgent, shouldConsiderOfflineAgent }: { shouldConsiderIdleAgent: boolean; shouldConsiderOfflineAgent: boolean },
): Promise<ILivechatVisitor | null> => {
if (!token) {
throw Error('error-invalid-token');
}
logger.debug({ msg: 'New incoming conversation', id, token });
const visitorDataToUpdate: Partial<ILivechatVisitor> & { userAgent?: string; ip?: string; host?: string } = {
token,
status,
...(phone?.number && { phone: [{ phoneNumber: phone.number }] }),
...(name && { name }),
...(externalIds?.length && { externalIds }),
};
if (email) {
const visitorEmail = email.trim().toLowerCase();
validateEmail(visitorEmail);
visitorDataToUpdate.visitorEmails = [{ address: visitorEmail }];
const contact = await LivechatContacts.findContactByEmailAndContactManager(visitorEmail);
if (contact?.contactManager) {
const agent = await Users.findOneOnlineAgentById(contact.contactManager, shouldConsiderIdleAgent, shouldConsiderOfflineAgent, {
projection: { _id: 1, username: 1, name: 1, emails: 1 },
});
if (agent?.username && agent.name && agent.emails) {
visitorDataToUpdate.contactManager = {
_id: agent._id,
username: agent.username,
name: agent.name,
emails: agent.emails,
};
logger.debug({ msg: 'Assigning visitor to agent', token, agent: agent.username });
}
}
}
const livechatVisitor = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1, department: 1, token: 1 } });
if (department && livechatVisitor?.department !== department) {
logger.debug({ msg: 'Attempt to find a department with id/name', department });
const dep = await LivechatDepartment.findOneByIdOrName(department, { projection: { _id: 1 } });
if (!dep) {
logger.debug({ msg: 'Invalid department provided', department });
// throw new Meteor.Error('error-invalid-department', 'The provided department is invalid');
throw new Error('error-invalid-department');
}
logger.debug({ msg: 'Assigning visitor to department', token, department: dep._id });
visitorDataToUpdate.department = dep._id;
}
visitorDataToUpdate.token = livechatVisitor?.token || token;
let existingUser = null;
if (livechatVisitor) {
logger.debug('Found matching user by token');
visitorDataToUpdate._id = livechatVisitor._id;
} else if (phone?.number && (existingUser = await LivechatVisitors.findOneVisitorByPhone(phone.number))) {
logger.debug('Found matching user by phone number');
visitorDataToUpdate._id = existingUser._id;
// Don't change token when matching by phone number, use current visitor token
visitorDataToUpdate.token = existingUser.token;
} else if (email && (existingUser = await LivechatVisitors.findOneGuestByEmailAddress(email))) {
logger.debug('Found matching user by email');
visitorDataToUpdate._id = existingUser._id;
} else if (!livechatVisitor) {
logger.debug({ msg: 'No matches found. Attempting to create new user with token', token });
visitorDataToUpdate._id = id || undefined;
visitorDataToUpdate.username = username || (await LivechatVisitors.getNextVisitorUsername());
visitorDataToUpdate.status = status;
visitorDataToUpdate.ts = new Date();
if (connectionData && typeof connectionData === 'object') {
logger.debug({ msg: 'Saving connection data for visitor', token });
const { httpHeaders, clientAddress } = connectionData;
if (httpHeaders && typeof httpHeaders === 'object') {
visitorDataToUpdate.userAgent = httpHeaders['user-agent'];
visitorDataToUpdate.ip = httpHeaders['x-real-ip'] || httpHeaders['x-forwarded-for'] || clientAddress;
visitorDataToUpdate.host = httpHeaders.host;
}
}
}
const upsertedLivechatVisitor = await LivechatVisitors.updateOneByIdOrToken(visitorDataToUpdate, {
upsert: true,
returnDocument: 'after',
});
if (!upsertedLivechatVisitor) {
logger.debug('No visitor found after upsert');
return null;
}
return upsertedLivechatVisitor;
},
);

View File

@@ -0,0 +1,9 @@
{
"extends": "@rocket.chat/tsconfig/server.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"declaration": true
},
"include": ["./src/**/*"],
}