Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
b9016c9
update logs
banshiAnton Mar 3, 2026
c4be3e5
sync activity cluster
banshiAnton Jan 21, 2026
81f8ede
update activity service
banshiAnton Mar 3, 2026
6473625
imp base clean node data
banshiAnton Mar 4, 2026
bd7907d
it can send offline status for died node users
banshiAnton Mar 5, 2026
3eb2c1a
refactoring
banshiAnton Mar 5, 2026
bc89bdc
fix spec
banshiAnton Mar 5, 2026
1638d70
update config
banshiAnton Mar 5, 2026
e455b0a
refactor PacketManager
banshiAnton Mar 6, 2026
b7739e5
it can clear destoyed node cache data
banshiAnton Mar 11, 2026
1ecf067
update clean node conditions
banshiAnton Mar 13, 2026
8bfe459
add reconnecting
banshiAnton Mar 16, 2026
c6c7200
add Node Clustering readme
banshiAnton Mar 17, 2026
7420a8a
it can close ws with code
banshiAnton Mar 19, 2026
822cd6f
reconnecty only ifWas opened
banshiAnton Mar 19, 2026
8438ec5
fix typo
banshiAnton Mar 20, 2026
482f4ab
start testing
banshiAnton Mar 31, 2026
b60acbd
add activty listening
banshiAnton Apr 1, 2026
7c60e88
update same-node / cross-node specs
banshiAnton Apr 1, 2026
9ae0711
add fixtures
banshiAnton Apr 1, 2026
89cb45e
update specs
banshiAnton Apr 2, 2026
73a5f85
it can connect with token
banshiAnton Apr 3, 2026
2211b82
test reconnecting
banshiAnton Apr 3, 2026
28a5f02
update specs
banshiAnton Apr 3, 2026
f6044ca
update specs: add dummy data
banshiAnton Apr 6, 2026
b3f9889
update spec titles
banshiAnton Apr 6, 2026
d51dbda
update install docker
banshiAnton Apr 7, 2026
317d59f
add hostname to stats
banshiAnton Apr 7, 2026
46ecc0a
update cors header
banshiAnton Apr 8, 2026
cdd60e8
add logs
banshiAnton Apr 9, 2026
d88c9b4
add log
banshiAnton Apr 9, 2026
3fc066f
add logs
banshiAnton Apr 9, 2026
51a5c6e
close cluster socket on delete
banshiAnton Apr 13, 2026
5e869a8
fix config
banshiAnton Apr 13, 2026
2ce353a
remove session with old node endpoint
banshiAnton Apr 15, 2026
330f2d4
add client cluster test
banshiAnton Apr 21, 2026
93c464f
update testing clients cluster
banshiAnton Apr 22, 2026
1b8f452
add check last activity
banshiAnton Apr 22, 2026
a17c794
add repl services
banshiAnton Apr 23, 2026
aff9f98
fix typo in env
banshiAnton Apr 23, 2026
8182ad3
fix env val
banshiAnton Apr 23, 2026
58fa564
update
banshiAnton Apr 23, 2026
20b1296
add netcat install to dockerfile
banshiAnton Apr 24, 2026
d79e6fc
update addUserDeviceConnection
banshiAnton May 11, 2026
abb0a39
add keep alive
banshiAnton May 12, 2026
0d3d4f4
Merge branch 'development' into imp-node-clustering
banshiAnton May 12, 2026
44a6798
fix typo
banshiAnton May 12, 2026
b8042ac
update submodule
banshiAnton May 14, 2026
3eab359
use res instead of res.fakeWsSessionKey
banshiAnton May 25, 2026
c300b85
watchdogPingSocket ignore HTTP_DEVICE_ID
banshiAnton May 25, 2026
c2939d6
update
banshiAnton May 26, 2026
9d440d3
try fix listUserDevice
banshiAnton Jun 1, 2026
3a569a2
try fix listUserDevice
banshiAnton Jun 1, 2026
8b61d59
update listUserDevice
banshiAnton Jun 2, 2026
0ea9038
add to session service filterActualDevices
banshiAnton Jun 3, 2026
95820d9
update addUserDeviceConnection and storeUserNodeData
banshiAnton Jun 4, 2026
16888cb
update package
banshiAnton Jun 4, 2026
b9b1dd4
add logs
banshiAnton Jun 9, 2026
5e8f745
add log
banshiAnton Jun 12, 2026
3483dca
it can block create_user/auth if account was blocked
banshiAnton Jun 17, 2026
f125643
update env
banshiAnton Jun 17, 2026
f2f134e
update @sama-communications/sdk
banshiAnton Jun 17, 2026
e9e6dae
update requestTrackId in error response
banshiAnton Jun 18, 2026
547d4d0
add cluster env CLUSTER_ADVERTISE_IP
banshiAnton Jun 18, 2026
44b6350
update workflows
banshiAnton Jun 18, 2026
e667eda
add workflow_dispatch
banshiAnton Jun 18, 2026
a80011c
update workflows tests
banshiAnton Jun 18, 2026
2775fdc
update env docker
banshiAnton Jun 18, 2026
93ec44f
formating
banshiAnton Jun 19, 2026
1aaca57
update submodule
banshiAnton Jul 7, 2026
31cce61
update submodule to main branch
banshiAnton Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .cluster-clients-mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"recursive": true,
"sort": false,
"color": true,
"allow-uncaught": true,
"parallel": false,
"reporter": "list",
"require": [
"dotenv/config"
],
"exit": true,
"spec": [
"./test/cluster/cluster-clients.spec.js"
],
"exclude": "test/**/*.spec.js",
"timeout": 120000
}
22 changes: 22 additions & 0 deletions .cluster-mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"recursive": true,
"sort": false,
"color": true,
"allow-uncaught": true,
"parallel": false,
"reporter": "list",
"require": [
"dotenv/config",
"./test/cluster/utils.js",
"./test/cluster/fixtures.js"
],
"exit": true,
"spec": [
"./test/cluster/same-node.spec.js",
"./test/cluster/cross-node.spec.js",
"./test/cluster/multi-devices.spec.js",
"./test/cluster/node-crash.spec.js"
],
"exclude": "test/**/*.spec.js",
"timeout": 120000
}
23 changes: 19 additions & 4 deletions .env.docker
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
NODE_ENV=development
NODE_ENV=testing

APP_PORT=9001

Expand All @@ -19,23 +19,38 @@ CONVERSATION_MAX_PARTICIPANTS=50
CONVERSATION_PRELOAD_COUNT=30
MESSAGE_PRELOAD_COUNT=30
SEARCH_PRELOAD_COUNT=30

JWT_ACCESS_SECRET=4c09be6a35bec6d4089cde4be5ca57a6ee85da5739579cbecb0b8253f268eca2
JWT_ACCESS_TOKEN_EXPIRES_IN=10800
JWT_REFRESH_TOKEN_EXPIRES_IN=1209600
JWT_REFRESH_SECRET=4c09be6a35bec6d4089cde4be5ca57a6ee85da5739579cbecb0b8253f268eca2

ENCRYPTION_MESSAGE_EXPIRED_IN=2592000 # 30 days
ENCRYPTION_DEVICE_TOKEN_EXPIRES_IN=2592000 # 30 days
ENCRYPTION_MESSAGE_EXPIRED_IN=2592000
ENCRYPTION_DEVICE_TOKEN_EXPIRES_IN=2592000

OPERATIONS_LOG_EXPIRES_IN=1209600
NODE_CLUSTER_DATA_EXPIRES_IN=30000
NODE_CLUSTER_DATA_EXPIRES_IN=10000

FILE_UPLOAD_URL_EXPIRES_IN=3600
FILE_DOWNLOAD_URL_EXPIRES_IN=604800
ENCRYPTION_MESSAGE_EXPIRED_IN=2592000
ENCRYPTION_DEVICE_TOKEN_EXPIRES_IN=2592000

SSL_KEY_FILE_NAME=
SSL_CERT_FILE_NAME=

# set this env if you want to override the os.hostname() value
HOSTNAME=NodeName
CORS_ORIGIN=http://localhost:10000
COOKIE_SECRET=4c09be6a35bec6d4089cde4be5ca57a6ee85da5739579cbecb0b8253f268eca2
HTTP_ADMIN_API_KEY=docker-secret-key

# Testing
RUN_NODE_1_CMD='APP_PORT=9001 APP_TCP_PORT=8001 CORS_ORIGIN=http://localhost:3001 npm start'
RUN_NODE_2_CMD='APP_PORT=9002 APP_TCP_PORT=8002 CORS_ORIGIN=http://localhost:3002 npm start'

NODE_1_WS_ENDPOINT=ws://localhost:9001
NODE_1_HTTP_ENDPOINT=http://localhost:9001

NODE_2_WS_ENDPOINT=ws://localhost:9002
NODE_2_HTTP_ENDPOINT=http://localhost:9002
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,19 @@ GOOGLE_GENERATIVE_AI_API_KEY=
SERVICE_OTP_TOKEN_EXPIRES_IN=300000
RESEND_SENDER="SAMASupport <onboarding@resend.dev>"
RESEND_API_KEY=


# Testing
RUN_NODE_1_CMD='APP_PORT=9001 APP_TCP_PORT=8001 CORS_ORIGIN=http://localhost:3001 npm start'
RUN_NODE_2_CMD='APP_PORT=9002 APP_TCP_PORT=8002 CORS_ORIGIN=http://localhost:3002 npm start'

NODE_1_WS_ENDPOINT=ws://localhost:9001
NODE_1_HTTP_ENDPOINT=http://localhost:9001

NODE_2_WS_ENDPOINT=ws://localhost:9002
NODE_2_HTTP_ENDPOINT=http://localhost:9002

TEST_CLIENTS_COUNT=10
TEST_CLIENT_ORG_ID=683db99d3874471b4dd36c69
TEST_CLIENT_WS_ENDPOINT=ws://localhost:9001
TEST_CLIENT_HTTP_ENDPOINT=http://localhost:9001
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ on:
branches:
- main

workflow_dispatch:

jobs:
run_tests:
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Build the SAMA resources
env:
NODE_ENV: testing
run: docker compose -f docker-compose-full.yml up -d --build

- name: Run migrations
run: docker compose exec sama-server sh -c "MONGODB_URL=mongodb://172.25.0.4/samatests npm run migrate-mongo-up"

- name: Run tests
- name: Run tests single node
run: docker compose exec sama-server sh -c "MONGODB_URL=mongodb://172.25.0.4/samatests npm run test"

- name: Stop the SAMA resources
Expand Down
1 change: 1 addition & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"node-option": ["experimental-loader=./sama-loader.mjs", "import=dotenv/config"],
"exit": true,
"spec": "test/**/*.spec.js",
"exclude": "test/cluster/*.spec.js",
"timeout": 60000
}
2 changes: 1 addition & 1 deletion APIs/JSON/controllers/http/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class HttpActivityController extends BaseHttpController {
const response = new Response()

const HttpActivityOnlineListOperation = ServiceLocatorContainer.use("HttpActivityOnlineListOperation")
const onlineListResponse = await HttpActivityOnlineListOperation.perform(res.fakeWsSessionKey, payload)
const onlineListResponse = await HttpActivityOnlineListOperation.perform(res, payload)

const responsePayload = isCountRequest ? { count: onlineListResponse } : { users: onlineListResponse }

Expand Down
14 changes: 9 additions & 5 deletions APIs/JSON/controllers/http/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class HttpAuthController extends BaseHttpController {
const httpAuthOperation = ServiceLocatorContainer.use("HttpUserAuthOperation")

const { user, newAccessToken, accessTokenExpiredAt, newRefreshToken } = await httpAuthOperation.perform(
res.fakeWsSessionKey,
res,
res.parsedHeaders,
res.parsedSignedCookies,
payload
Expand All @@ -43,7 +43,7 @@ class HttpAuthController extends BaseHttpController {
async logout(res, req) {
const httpLogoutOperation = ServiceLocatorContainer.use("HttpUserLogoutOperation")

const refreshTokenRecord = await httpLogoutOperation.perform(res.fakeWsSessionKey, res.parsedHeaders, res.parsedSignedCookies)
const { refreshTokenRecord, isWasLastUserSession } = await httpLogoutOperation.perform(res, res.parsedHeaders, res.parsedSignedCookies)

const httpResponse = new HttpResponse(200, {}, { success: true }).addCookie("refresh_token", refreshTokenRecord.token, {
maxAge: 0,
Expand All @@ -52,15 +52,19 @@ class HttpAuthController extends BaseHttpController {
sameSite: "lax",
})

return new Response()
.setHttpResponse(httpResponse)
.updateLastActivityStatus(
const response = new Response().setHttpResponse(httpResponse)

if (isWasLastUserSession) {
response.updateLastActivityStatus(
new LastActivityStatusResponse(
refreshTokenRecord.organization_id,
refreshTokenRecord.user_id,
MAIN_CONSTANTS.LAST_ACTIVITY_STATUS.OFFLINE
)
)
}

return response
}
}

Expand Down
15 changes: 6 additions & 9 deletions APIs/JSON/controllers/http/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class HttpMessageController extends BaseHttpController {
const response = new Response()

const httpMessageCreateOperation = ServiceLocatorContainer.use("HttpMessageCreateOperation")
const createMessageResponse = await httpMessageCreateOperation.perform(res.fakeWsSessionKey, payload)
const createMessageResponse = await httpMessageCreateOperation.perform(res, payload)
const { organizationId, messageId, message, deliverMessages, cId, participantIds, modifiedFields, botMessage } = createMessageResponse

deliverMessages.forEach((event) => {
Expand Down Expand Up @@ -48,7 +48,7 @@ class HttpMessageController extends BaseHttpController {
const payload = res.parsedBody

const messageSendSystemOperation = ServiceLocatorContainer.use("HttpMessageSendSystemOperation")
const { organizationId, cId, recipientsIds, systemMessage } = await messageSendSystemOperation.perform(res.fakeWsSessionKey, payload)
const { organizationId, cId, recipientsIds, systemMessage } = await messageSendSystemOperation.perform(res, payload)

const deliverMessage = new DeliverMessage(organizationId, new SystemMessageResponse(systemMessage), true)
.setConversationDestination(cId)
Expand All @@ -63,7 +63,7 @@ class HttpMessageController extends BaseHttpController {
const payload = res.parsedBody

const messageReadOperation = ServiceLocatorContainer.use("HttpMessageReadOperation")
const { organizationId, readMessagesGroups } = await messageReadOperation.perform(res.fakeWsSessionKey, payload)
const { organizationId, readMessagesGroups } = await messageReadOperation.perform(res, payload)

const response = new Response()

Expand All @@ -79,7 +79,7 @@ class HttpMessageController extends BaseHttpController {
const payload = res.parsedBody

const messageEditOperation = ServiceLocatorContainer.use("HttpMessageEditOperation")
const { organizationId, cId, participantsIds, editedMessage } = await messageEditOperation.perform(res.fakeWsSessionKey, payload)
const { organizationId, cId, participantsIds, editedMessage } = await messageEditOperation.perform(res, payload)

return new Response()
.setHttpResponse(new HttpResponse(200, {}, { success: true }))
Expand All @@ -94,10 +94,7 @@ class HttpMessageController extends BaseHttpController {
const payload = res.parsedBody

const messageReactionOperation = ServiceLocatorContainer.use("HttpMessageReactionOperation")
const { organizationId, cId, participantsIds, messageReactionsUpdate } = await messageReactionOperation.perform(
res.fakeWsSessionKey,
payload
)
const { organizationId, cId, participantsIds, messageReactionsUpdate } = await messageReactionOperation.perform(res, payload)

return new Response()
.setHttpResponse(new HttpResponse(200, {}, { success: true }))
Expand All @@ -112,7 +109,7 @@ class HttpMessageController extends BaseHttpController {
const payload = res.parsedBody

const messageDeleteOperation = ServiceLocatorContainer.use("HttpMessageDeleteOperation")
const { organizationId, cId, participantsIds, deletedMessages } = await messageDeleteOperation.perform(res.fakeWsSessionKey, payload)
const { organizationId, cId, participantsIds, deletedMessages } = await messageDeleteOperation.perform(res, payload)

const response = new Response()

Expand Down
2 changes: 1 addition & 1 deletion APIs/JSON/controllers/http/organization.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class HttpOrganizationController extends BaseHttpController {
const response = new Response()

const httpOrganizationCreateOperation = ServiceLocatorContainer.use("HttpOrganizationCreateOperation")
const organization = await httpOrganizationCreateOperation.perform(res.fakeWsSessionKey, payload)
const organization = await httpOrganizationCreateOperation.perform(res, payload)

return response.setHttpResponse(new HttpResponse(200, {}, { organization: organization.visibleParams() }))
}
Expand Down
2 changes: 1 addition & 1 deletion APIs/JSON/controllers/http/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class HttpServerStatsController extends BaseHttpController {
const response = new Response()

const httpOrganizationCreateOperation = ServiceLocatorContainer.use("HttpStatsCollectOperation")
const stats = await httpOrganizationCreateOperation.perform(res.fakeWsSessionKey, payload)
const stats = await httpOrganizationCreateOperation.perform(res, payload)

return response.setHttpResponse(new HttpResponse(200, {}, stats))
}
Expand Down
12 changes: 8 additions & 4 deletions APIs/JSON/controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ class UsersController extends BaseJSONController {
const { id: requestId } = data

const userLogoutOperation = ServiceLocatorContainer.use("UserLogoutOperation")
const { organizationId, userId } = await userLogoutOperation.perform(ws)
const { organizationId, userId, isWasLastUserSession } = await userLogoutOperation.perform(ws)

return new Response()
.addBackMessage({ response: { id: requestId, success: true } })
.updateLastActivityStatus(new LastActivityStatusResponse(organizationId, userId, MAIN_CONSTANTS.LAST_ACTIVITY_STATUS.OFFLINE))
const response = new Response().addBackMessage({ response: { id: requestId, success: true } })

if (isWasLastUserSession) {
response.updateLastActivityStatus(new LastActivityStatusResponse(organizationId, userId, MAIN_CONSTANTS.LAST_ACTIVITY_STATUS.OFFLINE))
}

return response
}

async send_otp(ws, data) {
Expand Down
8 changes: 5 additions & 3 deletions APIs/JSON/routes/packet_processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ class PacketJsonProcessor extends BasePacketProcessor {
async processMessageOrError(ws, message) {
let responseData
let json
let requestTrackId
try {
json = this.#parseMessage(message)
requestTrackId = json?.request?.id ?? json?.message?.id ?? json?.system_message?.id ?? json?.id
updateStoreContext(
MAIN_CONSTANTS.LOGGER_BINDINGS_NAMES.REQUEST_ID,
json?.request?.id ?? json?.message?.id ?? json?.system_message?.id ?? json?.id ?? MAIN_CONSTANTS.LOGGER_BINDINGS_NAMES.NO_REQUEST_ID
requestTrackId ?? MAIN_CONSTANTS.LOGGER_BINDINGS_NAMES.NO_REQUEST_ID
)
responseData = await this.#processMessage(ws, json)
if (!responseData) {
Expand All @@ -49,15 +51,15 @@ class PacketJsonProcessor extends BasePacketProcessor {
if (json?.request) {
errorBackMessage = {
response: {
id: json.request?.id,
id: json.request?.id ?? requestTrackId,
error: error.cause || error.message,
},
}
} else {
const topLevelElement = json ? Object.keys(json)[0] : void 0
errorBackMessage = {
[topLevelElement]: {
id: json?.[topLevelElement]?.id,
id: json?.[topLevelElement]?.id ?? requestTrackId,
error: error.cause || error.message,
},
}
Expand Down
2 changes: 1 addition & 1 deletion APIs/XMPP
Submodule XMPP updated from 7345b5 to 53e1ab
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /app

COPY package*.json ./

RUN npm install
RUN npm install --omit=dev

COPY . .

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.local
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /app

COPY package*.json ./

RUN npm install
RUN npm install

COPY . .

Expand Down
Loading