feat(auth): remove websocket handling in favor of websockets service
This commit is contained in:
parent
863e8555ae
commit
86ae4a59a3
31 changed files with 81 additions and 457 deletions
|
@ -7,6 +7,7 @@ PORT=3000
|
|||
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
|
||||
AUTH_SERVER_URL=http://auth:3000
|
||||
WORKSPACE_SERVER_URL=http://workspace:3000
|
||||
WEB_SOCKET_SERVER_URL=http://websockets:3000
|
||||
PAYMENTS_SERVER_URL=http://payments:3000
|
||||
FILES_SERVER_URL=http://files:3000
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ export class ContainerConfigLoader {
|
|||
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
||||
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL'))
|
||||
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL'))
|
||||
container
|
||||
.bind(TYPES.HTTP_CALL_TIMEOUT)
|
||||
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
|
||||
|
|
|
@ -9,6 +9,7 @@ const TYPES = {
|
|||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
||||
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
|
||||
VERSION: Symbol.for('VERSION'),
|
||||
|
|
|
@ -17,7 +17,7 @@ export class WebSocketsController extends BaseHttpController {
|
|||
|
||||
@httpPost('/tokens', TYPES.AuthMiddleware)
|
||||
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'sockets/tokens', request.body)
|
||||
await this.httpService.callWebSocketServer(request, response, 'sockets/tokens', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/connections', TYPES.WebSocketAuthMiddleware)
|
||||
|
@ -30,7 +30,7 @@ export class WebSocketsController extends BaseHttpController {
|
|||
return
|
||||
}
|
||||
|
||||
await this.httpService.callAuthServer(
|
||||
await this.httpService.callWebSocketServer(
|
||||
request,
|
||||
response,
|
||||
`sockets/connections/${request.headers.connectionid}`,
|
||||
|
@ -48,7 +48,7 @@ export class WebSocketsController extends BaseHttpController {
|
|||
return
|
||||
}
|
||||
|
||||
await this.httpService.callAuthServer(
|
||||
await this.httpService.callWebSocketServer(
|
||||
request,
|
||||
response,
|
||||
`sockets/connections/${request.headers.connectionid}`,
|
||||
|
|
|
@ -17,6 +17,7 @@ export class HttpService implements HttpServiceInterface {
|
|||
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
|
||||
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
|
||||
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
||||
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
||||
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
|
@ -58,6 +59,15 @@ export class HttpService implements HttpServiceInterface {
|
|||
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callPaymentsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
|
|
@ -37,4 +37,10 @@ export interface HttpServiceInterface {
|
|||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
}
|
||||
|
|
|
@ -67,7 +67,6 @@ VALET_TOKEN_SECRET=
|
|||
VALET_TOKEN_TTL=
|
||||
|
||||
WEB_SOCKET_CONNECTION_TOKEN_SECRET=
|
||||
WEB_SOCKET_CONNECTION_TOKEN_TTL=
|
||||
|
||||
# (Optional) Analytics
|
||||
ANALYTICS_ENABLED=false
|
||||
|
|
|
@ -79,10 +79,6 @@ import { DeleteAccount } from '../Domain/UseCase/DeleteAccount/DeleteAccount'
|
|||
import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting'
|
||||
import { SettingFactory } from '../Domain/Setting/SettingFactory'
|
||||
import { SettingService } from '../Domain/Setting/SettingService'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { RedisWebSocketsConnectionRepository } from '../Infra/Redis/RedisWebSocketsConnectionRepository'
|
||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
import { UserSubscription } from '../Domain/Subscription/UserSubscription'
|
||||
import { MySQLUserSubscriptionRepository } from '../Infra/MySQL/MySQLUserSubscriptionRepository'
|
||||
|
@ -206,9 +202,6 @@ import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventH
|
|||
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
|
||||
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
||||
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
import { WebSocketsController } from '../Controller/WebSocketsController'
|
||||
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
@ -273,7 +266,6 @@ export class ContainerConfigLoader {
|
|||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
|
||||
|
||||
// Repositories
|
||||
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
|
||||
|
@ -295,9 +287,6 @@ export class ContainerConfigLoader {
|
|||
.bind<RedisEphemeralSessionRepository>(TYPES.EphemeralSessionRepository)
|
||||
.to(RedisEphemeralSessionRepository)
|
||||
container.bind<LockRepository>(TYPES.LockRepository).to(LockRepository)
|
||||
container
|
||||
.bind<WebSocketsConnectionRepositoryInterface>(TYPES.WebSocketsConnectionRepository)
|
||||
.to(RedisWebSocketsConnectionRepository)
|
||||
container
|
||||
.bind<SubscriptionTokenRepositoryInterface>(TYPES.SubscriptionTokenRepository)
|
||||
.to(RedisSubscriptionTokenRepository)
|
||||
|
@ -375,9 +364,6 @@ export class ContainerConfigLoader {
|
|||
container
|
||||
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)
|
||||
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true))
|
||||
container
|
||||
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
|
||||
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
|
||||
container.bind(TYPES.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
|
||||
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
|
||||
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_TOKEN_AGE'))
|
||||
|
@ -399,7 +385,6 @@ export class ContainerConfigLoader {
|
|||
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container.bind(TYPES.SYNCING_SERVER_URL).toConstantValue(env.get('SYNCING_SERVER_URL'))
|
||||
container.bind(TYPES.WEBSOCKETS_API_URL).toConstantValue(env.get('WEBSOCKETS_API_URL', true))
|
||||
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
|
||||
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
||||
|
||||
|
@ -424,8 +409,6 @@ export class ContainerConfigLoader {
|
|||
container.bind<UpdateSetting>(TYPES.UpdateSetting).to(UpdateSetting)
|
||||
container.bind<DeleteSetting>(TYPES.DeleteSetting).to(DeleteSetting)
|
||||
container.bind<DeleteAccount>(TYPES.DeleteAccount).to(DeleteAccount)
|
||||
container.bind<AddWebSocketsConnection>(TYPES.AddWebSocketsConnection).to(AddWebSocketsConnection)
|
||||
container.bind<RemoveWebSocketsConnection>(TYPES.RemoveWebSocketsConnection).to(RemoveWebSocketsConnection)
|
||||
container.bind<GetUserSubscription>(TYPES.GetUserSubscription).to(GetUserSubscription)
|
||||
container.bind<GetUserOfflineSubscription>(TYPES.GetUserOfflineSubscription).to(GetUserOfflineSubscription)
|
||||
container.bind<CreateSubscriptionToken>(TYPES.CreateSubscriptionToken).to(CreateSubscriptionToken)
|
||||
|
@ -454,9 +437,6 @@ export class ContainerConfigLoader {
|
|||
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
|
||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
|
||||
container
|
||||
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
|
||||
.to(CreateWebSocketConnectionToken)
|
||||
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||
|
||||
// Handlers
|
||||
|
@ -547,11 +527,6 @@ export class ContainerConfigLoader {
|
|||
container
|
||||
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
|
||||
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET)))
|
||||
container
|
||||
.bind<TokenEncoderInterface<WebSocketConnectionTokenData>>(TYPES.WebSocketConnectionTokenEncoder)
|
||||
.toConstantValue(
|
||||
new TokenEncoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
|
||||
)
|
||||
container.bind<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
|
||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
|
||||
|
|
|
@ -6,7 +6,6 @@ const TYPES = {
|
|||
// Controller
|
||||
AuthController: Symbol.for('AuthController'),
|
||||
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||
WebSocketsController: Symbol.for('WebSocketsController'),
|
||||
// Repositories
|
||||
UserRepository: Symbol.for('UserRepository'),
|
||||
SessionRepository: Symbol.for('SessionRepository'),
|
||||
|
@ -17,7 +16,6 @@ const TYPES = {
|
|||
OfflineSettingRepository: Symbol.for('OfflineSettingRepository'),
|
||||
LockRepository: Symbol.for('LockRepository'),
|
||||
RoleRepository: Symbol.for('RoleRepository'),
|
||||
WebSocketsConnectionRepository: Symbol.for('WebSocketsConnectionRepository'),
|
||||
UserSubscriptionRepository: Symbol.for('UserSubscriptionRepository'),
|
||||
OfflineUserSubscriptionRepository: Symbol.for('OfflineUserSubscriptionRepository'),
|
||||
SubscriptionTokenRepository: Symbol.for('SubscriptionTokenRepository'),
|
||||
|
@ -83,7 +81,6 @@ const TYPES = {
|
|||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
SYNCING_SERVER_URL: Symbol.for('SYNCING_SERVER_URL'),
|
||||
WEBSOCKETS_API_URL: Symbol.for('WEBSOCKETS_API_URL'),
|
||||
VERSION: Symbol.for('VERSION'),
|
||||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||
// use cases
|
||||
|
@ -107,8 +104,6 @@ const TYPES = {
|
|||
UpdateSetting: Symbol.for('UpdateSetting'),
|
||||
DeleteSetting: Symbol.for('DeleteSetting'),
|
||||
DeleteAccount: Symbol.for('DeleteAccount'),
|
||||
AddWebSocketsConnection: Symbol.for('AddWebSocketsConnection'),
|
||||
RemoveWebSocketsConnection: Symbol.for('RemoveWebSocketsConnection'),
|
||||
GetUserSubscription: Symbol.for('GetUserSubscription'),
|
||||
GetUserOfflineSubscription: Symbol.for('GetUserOfflineSubscription'),
|
||||
CreateSubscriptionToken: Symbol.for('CreateSubscriptionToken'),
|
||||
|
@ -125,7 +120,6 @@ const TYPES = {
|
|||
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
|
||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||
VerifyPredicate: Symbol.for('VerifyPredicate'),
|
||||
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
|
||||
CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
|
@ -168,7 +162,6 @@ const TYPES = {
|
|||
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
|
||||
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
|
||||
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
|
||||
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
|
||||
WebSocketConnectionTokenDecoder: Symbol.for('WebSocketConnectionTokenDecoder'),
|
||||
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import 'reflect-metadata'
|
||||
|
||||
import { WebSocketsController } from './WebSocketsController'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
describe('WebSocketsController', () => {
|
||||
let createWebSocketConnectionToken: CreateWebSocketConnectionToken
|
||||
|
||||
const createController = () => new WebSocketsController(createWebSocketConnectionToken)
|
||||
|
||||
beforeEach(() => {
|
||||
createWebSocketConnectionToken = {} as jest.Mocked<CreateWebSocketConnectionToken>
|
||||
createWebSocketConnectionToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
|
||||
})
|
||||
|
||||
it('should create a web sockets connection token', async () => {
|
||||
const response = await createController().createConnectionToken({ userUuid: '1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { token: 'foobar' },
|
||||
})
|
||||
|
||||
expect(createWebSocketConnectionToken.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,29 +0,0 @@
|
|||
import {
|
||||
HttpStatusCode,
|
||||
WebSocketConnectionTokenRequestParams,
|
||||
WebSocketConnectionTokenResponse,
|
||||
WebSocketServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketsController implements WebSocketServerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.CreateWebSocketConnectionToken)
|
||||
private createWebSocketConnectionToken: CreateWebSocketConnectionToken,
|
||||
) {}
|
||||
|
||||
async createConnectionToken(
|
||||
params: WebSocketConnectionTokenRequestParams,
|
||||
): Promise<WebSocketConnectionTokenResponse> {
|
||||
const result = await this.createWebSocketConnectionToken.execute({ userUuid: params.userUuid as string })
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,29 @@ describe('DomainEventFactory', () => {
|
|||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
||||
})
|
||||
|
||||
it('should create a WEB_SOCKET_MESSAGE_REQUESTED event', () => {
|
||||
expect(
|
||||
createFactory().createWebSocketMessageRequestedEvent({
|
||||
userUuid: '1-2-3',
|
||||
message: 'foobar',
|
||||
}),
|
||||
).toEqual({
|
||||
createdAt: expect.any(Date),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: '1-2-3',
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: 'auth',
|
||||
},
|
||||
payload: {
|
||||
userUuid: '1-2-3',
|
||||
message: 'foobar',
|
||||
},
|
||||
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a EMAIL_MESSAGE_REQUESTED event', () => {
|
||||
expect(
|
||||
createFactory().createEmailMessageRequestedEvent({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { EmailMessageIdentifier, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
||||
import { EmailMessageIdentifier, JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
||||
import {
|
||||
AccountDeletionRequestedEvent,
|
||||
UserEmailChangedEvent,
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
PredicateVerifiedEvent,
|
||||
DomainEventService,
|
||||
EmailMessageRequestedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
@ -27,6 +28,21 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent {
|
||||
return {
|
||||
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createEmailMessageRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: EmailMessageIdentifier
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Uuid, RoleName, EmailMessageIdentifier, ProtocolVersion } from '@standardnotes/common'
|
||||
import { Uuid, RoleName, EmailMessageIdentifier, ProtocolVersion, JSONString } from '@standardnotes/common'
|
||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||
import {
|
||||
AccountDeletionRequestedEvent,
|
||||
|
@ -15,10 +15,12 @@ import {
|
|||
SharedSubscriptionInvitationCanceledEvent,
|
||||
PredicateVerifiedEvent,
|
||||
EmailMessageRequestedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
|
||||
createEmailMessageRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: EmailMessageIdentifier
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
|
||||
import { AddWebSocketsConnection } from './AddWebSocketsConnection'
|
||||
|
||||
describe('AddWebSocketsConnection', () => {
|
||||
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new AddWebSocketsConnection(webSocketsConnectionRepository, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
|
||||
webSocketsConnectionRepository.saveConnection = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
})
|
||||
|
||||
it('should save a web sockets connection for a user for further communication', async () => {
|
||||
await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
|
||||
expect(webSocketsConnectionRepository.saveConnection).toHaveBeenCalledWith('1-2-3', '2-3-4')
|
||||
})
|
||||
})
|
|
@ -1,26 +0,0 @@
|
|||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { AddWebSocketsConnectionDTO } from './AddWebSocketsConnectionDTO'
|
||||
import { AddWebSocketsConnectionResponse } from './AddWebSocketsConnectionResponse'
|
||||
|
||||
@injectable()
|
||||
export class AddWebSocketsConnection implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddWebSocketsConnectionDTO): Promise<AddWebSocketsConnectionResponse> {
|
||||
this.logger.debug(`Persisting connection ${dto.connectionId} for user ${dto.userUuid}`)
|
||||
|
||||
await this.webSocketsConnectionRepository.saveConnection(dto.userUuid, dto.connectionId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export type AddWebSocketsConnectionDTO = {
|
||||
userUuid: string
|
||||
connectionId: string
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type AddWebSocketsConnectionResponse = {
|
||||
success: boolean
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
import 'reflect-metadata'
|
||||
|
||||
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||
|
||||
import { CreateWebSocketConnectionToken } from './CreateWebSocketConnectionToken'
|
||||
|
||||
describe('CreateWebSocketConnection', () => {
|
||||
let tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>
|
||||
const tokenTTL = 30
|
||||
|
||||
const createUseCase = () => new CreateWebSocketConnectionToken(tokenEncoder, tokenTTL)
|
||||
|
||||
beforeEach(() => {
|
||||
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<WebSocketConnectionTokenData>>
|
||||
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
|
||||
})
|
||||
|
||||
it('should create a web socket connection token', async () => {
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3' })
|
||||
|
||||
expect(result.token).toEqual('foobar')
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3' }, 30)
|
||||
})
|
||||
})
|
|
@ -1,3 +0,0 @@
|
|||
export type CreateWebSocketConnectionDTO = {
|
||||
userUuid: string
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type CreateWebSocketConnectionResponse = {
|
||||
token: string
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { CreateWebSocketConnectionDTO } from './CreateWebSocketConnectionDTO'
|
||||
import { CreateWebSocketConnectionResponse } from './CreateWebSocketConnectionResponse'
|
||||
|
||||
@injectable()
|
||||
export class CreateWebSocketConnectionToken implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketConnectionTokenEncoder)
|
||||
private tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>,
|
||||
@inject(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL) private tokenTTL: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateWebSocketConnectionDTO): Promise<CreateWebSocketConnectionResponse> {
|
||||
const data: WebSocketConnectionTokenData = {
|
||||
userUuid: dto.userUuid,
|
||||
}
|
||||
|
||||
return {
|
||||
token: this.tokenEncoder.encodeExpirableToken(data, this.tokenTTL),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
|
||||
import { RemoveWebSocketsConnection } from './RemoveWebSocketsConnection'
|
||||
|
||||
describe('RemoveWebSocketsConnection', () => {
|
||||
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new RemoveWebSocketsConnection(webSocketsConnectionRepository, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
|
||||
webSocketsConnectionRepository.removeConnection = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
})
|
||||
|
||||
it('should remove a web sockets connection', async () => {
|
||||
await createUseCase().execute({ connectionId: '2-3-4' })
|
||||
|
||||
expect(webSocketsConnectionRepository.removeConnection).toHaveBeenCalledWith('2-3-4')
|
||||
})
|
||||
})
|
|
@ -1,26 +0,0 @@
|
|||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { RemoveWebSocketsConnectionDTO } from './RemoveWebSocketsConnectionDTO'
|
||||
import { RemoveWebSocketsConnectionResponse } from './RemoveWebSocketsConnectionResponse'
|
||||
|
||||
@injectable()
|
||||
export class RemoveWebSocketsConnection implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: RemoveWebSocketsConnectionDTO): Promise<RemoveWebSocketsConnectionResponse> {
|
||||
this.logger.debug(`Removing connection ${dto.connectionId}`)
|
||||
|
||||
await this.webSocketsConnectionRepository.removeConnection(dto.connectionId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type RemoveWebSocketsConnectionDTO = {
|
||||
connectionId: string
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export type RemoveWebSocketsConnectionResponse = {
|
||||
success: boolean
|
||||
}
|
|
@ -1,43 +1,27 @@
|
|||
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/common'
|
||||
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||
import { Request, Response } from 'express'
|
||||
import { Request } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpDelete,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AddWebSocketsConnection } from '../../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||
import { RemoveWebSocketsConnection } from '../../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
|
||||
@controller('/sockets')
|
||||
export class InversifyExpressWebSocketsController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
|
||||
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
|
||||
@inject(TYPES.CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken,
|
||||
@inject(TYPES.WebSocketsController) private webSocketsController: WebSocketServerInterface,
|
||||
@inject(TYPES.WebSocketConnectionTokenDecoder)
|
||||
private tokenDecoder: TokenDecoderInterface<WebSocketConnectionTokenData>,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/tokens', TYPES.ApiGatewayAuthMiddleware)
|
||||
async createConnectionToken(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.webSocketsController.createConnectionToken({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpPost('/tokens/validate')
|
||||
async validateToken(request: Request): Promise<results.JsonResult> {
|
||||
if (!request.headers.authorization) {
|
||||
|
@ -72,26 +56,4 @@ export class InversifyExpressWebSocketsController extends BaseHttpController {
|
|||
|
||||
return this.json({ authToken: result.token })
|
||||
}
|
||||
|
||||
@httpPost('/connections/:connectionId', TYPES.ApiGatewayAuthMiddleware)
|
||||
async storeWebSocketsConnection(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.addWebSocketsConnection.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
connectionId: request.params.connectionId,
|
||||
})
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpDelete('/connections/:connectionId')
|
||||
async deleteWebSocketsConnection(
|
||||
request: Request,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import 'reflect-metadata'
|
||||
|
||||
import * as IORedis from 'ioredis'
|
||||
|
||||
import { RedisWebSocketsConnectionRepository } from './RedisWebSocketsConnectionRepository'
|
||||
|
||||
describe('RedisWebSocketsConnectionRepository', () => {
|
||||
let redisClient: IORedis.Redis
|
||||
|
||||
const createRepository = () => new RedisWebSocketsConnectionRepository(redisClient)
|
||||
|
||||
beforeEach(() => {
|
||||
redisClient = {} as jest.Mocked<IORedis.Redis>
|
||||
redisClient.sadd = jest.fn()
|
||||
redisClient.set = jest.fn()
|
||||
redisClient.get = jest.fn()
|
||||
redisClient.srem = jest.fn()
|
||||
redisClient.del = jest.fn()
|
||||
redisClient.smembers = jest.fn()
|
||||
})
|
||||
|
||||
it('should save a connection to set of user connections', async () => {
|
||||
await createRepository().saveConnection('1-2-3', '2-3-4')
|
||||
|
||||
expect(redisClient.sadd).toHaveBeenCalledWith('ws_user_connections:1-2-3', '2-3-4')
|
||||
expect(redisClient.set).toHaveBeenCalledWith('ws_connection:2-3-4', '1-2-3')
|
||||
})
|
||||
|
||||
it('should remove a connection from the set of user connections', async () => {
|
||||
redisClient.get = jest.fn().mockReturnValue('1-2-3')
|
||||
|
||||
await createRepository().removeConnection('2-3-4')
|
||||
|
||||
expect(redisClient.srem).toHaveBeenCalledWith('ws_user_connections:1-2-3', '2-3-4')
|
||||
expect(redisClient.del).toHaveBeenCalledWith('ws_connection:2-3-4')
|
||||
})
|
||||
|
||||
it('should return all connections for a user uuid', async () => {
|
||||
const userUuid = '1-2-3'
|
||||
|
||||
await createRepository().findAllByUserUuid(userUuid)
|
||||
expect(redisClient.smembers).toHaveBeenCalledWith(`ws_user_connections:${userUuid}`)
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
import * as IORedis from 'ioredis'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class RedisWebSocketsConnectionRepository implements WebSocketsConnectionRepositoryInterface {
|
||||
private readonly WEB_SOCKETS_USER_CONNECTIONS_PREFIX = 'ws_user_connections'
|
||||
private readonly WEB_SOCKETS_CONNETION_PREFIX = 'ws_connection'
|
||||
|
||||
constructor(@inject(TYPES.Redis) private redisClient: IORedis.Redis) {}
|
||||
|
||||
async findAllByUserUuid(userUuid: string): Promise<string[]> {
|
||||
return await this.redisClient.smembers(`${this.WEB_SOCKETS_USER_CONNECTIONS_PREFIX}:${userUuid}`)
|
||||
}
|
||||
|
||||
async removeConnection(connectionId: string): Promise<void> {
|
||||
const userUuid = await this.redisClient.get(`${this.WEB_SOCKETS_CONNETION_PREFIX}:${connectionId}`)
|
||||
|
||||
await this.redisClient.srem(`${this.WEB_SOCKETS_USER_CONNECTIONS_PREFIX}:${userUuid}`, connectionId)
|
||||
await this.redisClient.del(`${this.WEB_SOCKETS_CONNETION_PREFIX}:${connectionId}`)
|
||||
}
|
||||
|
||||
async saveConnection(userUuid: string, connectionId: string): Promise<void> {
|
||||
await this.redisClient.set(`${this.WEB_SOCKETS_CONNETION_PREFIX}:${connectionId}`, userUuid)
|
||||
await this.redisClient.sadd(`${this.WEB_SOCKETS_USER_CONNECTIONS_PREFIX}:${userUuid}`, connectionId)
|
||||
}
|
||||
}
|
|
@ -1,38 +1,21 @@
|
|||
import 'reflect-metadata'
|
||||
|
||||
import { UserRolesChangedEvent } from '@standardnotes/domain-events'
|
||||
import { DomainEventPublisherInterface, UserRolesChangedEvent } from '@standardnotes/domain-events'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
|
||||
import { User } from '../../Domain/User/User'
|
||||
import { WebSocketsClientService } from './WebSocketsClientService'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface'
|
||||
import { AxiosInstance } from 'axios'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('WebSocketsClientService', () => {
|
||||
let connectionIds: string[]
|
||||
let user: User
|
||||
let event: UserRolesChangedEvent
|
||||
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let httpClient: AxiosInstance
|
||||
let logger: Logger
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
let webSocketsApiUrl = 'http://test-websockets'
|
||||
|
||||
const createService = () =>
|
||||
new WebSocketsClientService(
|
||||
webSocketsConnectionRepository,
|
||||
domainEventFactory,
|
||||
httpClient,
|
||||
webSocketsApiUrl,
|
||||
logger,
|
||||
)
|
||||
const createService = () => new WebSocketsClientService(domainEventFactory, domainEventPublisher)
|
||||
|
||||
beforeEach(() => {
|
||||
connectionIds = ['1', '2']
|
||||
|
||||
user = {
|
||||
uuid: '123',
|
||||
email: 'test@test.com',
|
||||
|
@ -45,43 +28,19 @@ describe('WebSocketsClientService', () => {
|
|||
|
||||
event = {} as jest.Mocked<UserRolesChangedEvent>
|
||||
|
||||
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
|
||||
webSocketsConnectionRepository.findAllByUserUuid = jest.fn().mockReturnValue(connectionIds)
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createUserRolesChangedEvent = jest.fn().mockReturnValue(event)
|
||||
|
||||
httpClient = {} as jest.Mocked<AxiosInstance>
|
||||
httpClient.request = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
})
|
||||
|
||||
it('should send a user role changed event to all user connections', async () => {
|
||||
it('should request a message about a user role changed', async () => {
|
||||
await createService().sendUserRolesChangedEvent(user)
|
||||
|
||||
expect(domainEventFactory.createUserRolesChangedEvent).toHaveBeenCalledWith('123', 'test@test.com', [
|
||||
RoleName.ProUser,
|
||||
])
|
||||
expect(httpClient.request).toHaveBeenCalledTimes(connectionIds.length)
|
||||
connectionIds.map((id, index) => {
|
||||
expect(httpClient.request).toHaveBeenNthCalledWith(
|
||||
index + 1,
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
url: `${webSocketsApiUrl}/${id}`,
|
||||
data: JSON.stringify(event),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not send a user role changed event if web sockets api url not defined', async () => {
|
||||
webSocketsApiUrl = ''
|
||||
|
||||
await createService().sendUserRolesChangedEvent(user)
|
||||
|
||||
expect(httpClient.request).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,52 +1,31 @@
|
|||
import { AxiosInstance } from 'axios'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface'
|
||||
import { User } from '../../Domain/User/User'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketsClientService implements ClientServiceInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
|
||||
@inject(TYPES.WEBSOCKETS_API_URL) private webSocketsApiUrl: string,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async sendUserRolesChangedEvent(user: User): Promise<void> {
|
||||
if (!this.webSocketsApiUrl) {
|
||||
this.logger.debug('Web Sockets API url not defined. Skipped sending user role changed event.')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const userConnections = await this.webSocketsConnectionRepository.findAllByUserUuid(user.uuid)
|
||||
const event = this.domainEventFactory.createUserRolesChangedEvent(
|
||||
user.uuid,
|
||||
user.email,
|
||||
(await user.roles).map((role) => role.name) as RoleName[],
|
||||
)
|
||||
|
||||
for (const connectionUuid of userConnections) {
|
||||
await this.httpClient.request({
|
||||
method: 'POST',
|
||||
url: `${this.webSocketsApiUrl}/${connectionUuid}`,
|
||||
headers: {
|
||||
Accept: 'text/plain',
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
data: JSON.stringify(event),
|
||||
validateStatus:
|
||||
/* istanbul ignore next */
|
||||
(status: number) => status >= 200 && status < 500,
|
||||
})
|
||||
}
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: user.uuid,
|
||||
message: JSON.stringify(event),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue