Compare commits

..

8 Commits

Author SHA1 Message Date
standardci c0cb7f7a92 chore(release): publish new version
- @standardnotes/api-gateway@1.31.1
 - @standardnotes/auth-server@1.45.0
 - @standardnotes/domain-events-infra@1.8.27
 - @standardnotes/domain-events@2.68.0
 - @standardnotes/event-store@1.4.6
 - @standardnotes/files-server@1.6.18
 - @standardnotes/scheduler-server@1.10.46
 - @standardnotes/syncing-server@1.9.8
 - @standardnotes/websockets-server@1.1.3
 - @standardnotes/workspace-server@1.14.0
2022-10-13 10:52:01 +00:00
Karol Sójko 86379eb96d feat: publish workspace invite accepted event for websockets 2022-10-13 12:50:10 +02:00
standardci f7762a97e3 chore(release): publish new version
- @standardnotes/api-gateway@1.31.0
 - @standardnotes/auth-server@1.44.0
2022-10-13 10:25:22 +00:00
Karol Sójko 86ae4a59a3 feat(auth): remove websocket handling in favor of websockets service 2022-10-13 12:23:14 +02:00
standardci 863e8555ae chore(release): publish new version
- @standardnotes/websockets-server@1.1.2
2022-10-13 10:11:43 +00:00
Karol Sójko 4e21edce6b fix(websockets): add http client binding 2022-10-13 12:09:20 +02:00
standardci 5663841145 chore(release): publish new version
- @standardnotes/websockets-server@1.1.1
2022-10-13 09:59:16 +00:00
Karol Sójko 2f7ef497ab fix(websockets): remove unnecessary sns bindings 2022-10-13 11:57:09 +02:00
61 changed files with 328 additions and 484 deletions
+1
View File
@@ -7,6 +7,7 @@ PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000 SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
AUTH_SERVER_URL=http://auth:3000 AUTH_SERVER_URL=http://auth:3000
WORKSPACE_SERVER_URL=http://workspace:3000 WORKSPACE_SERVER_URL=http://workspace:3000
WEB_SOCKET_SERVER_URL=http://websockets:3000
PAYMENTS_SERVER_URL=http://payments:3000 PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000 FILES_SERVER_URL=http://files:3000
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.31.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.31.0...@standardnotes/api-gateway@1.31.1) (2022-10-13)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.31.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.30.1...@standardnotes/api-gateway@1.31.0) (2022-10-13)
### Features
* **auth:** remove websocket handling in favor of websockets service ([86ae4a5](https://github.com/standardnotes/api-gateway/commit/86ae4a59a3ac7915ad96ed5176b545f4d005e837))
## [1.30.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.30.0...@standardnotes/api-gateway@1.30.1) (2022-10-13) ## [1.30.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.30.0...@standardnotes/api-gateway@1.30.1) (2022-10-13)
**Note:** Version bump only for package @standardnotes/api-gateway **Note:** Version bump only for package @standardnotes/api-gateway
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/api-gateway", "name": "@standardnotes/api-gateway",
"version": "1.30.1", "version": "1.31.1",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
@@ -76,6 +76,7 @@ export class ContainerConfigLoader {
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true)) 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.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL')) 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 container
.bind(TYPES.HTTP_CALL_TIMEOUT) .bind(TYPES.HTTP_CALL_TIMEOUT)
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000) .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'), PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'), FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_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'), AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'), HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
VERSION: Symbol.for('VERSION'), VERSION: Symbol.for('VERSION'),
@@ -17,7 +17,7 @@ export class WebSocketsController extends BaseHttpController {
@httpPost('/tokens', TYPES.AuthMiddleware) @httpPost('/tokens', TYPES.AuthMiddleware)
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> { 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) @httpPost('/connections', TYPES.WebSocketAuthMiddleware)
@@ -30,7 +30,7 @@ export class WebSocketsController extends BaseHttpController {
return return
} }
await this.httpService.callAuthServer( await this.httpService.callWebSocketServer(
request, request,
response, response,
`sockets/connections/${request.headers.connectionid}`, `sockets/connections/${request.headers.connectionid}`,
@@ -48,7 +48,7 @@ export class WebSocketsController extends BaseHttpController {
return return
} }
await this.httpService.callAuthServer( await this.httpService.callWebSocketServer(
request, request,
response, response,
`sockets/connections/${request.headers.connectionid}`, `sockets/connections/${request.headers.connectionid}`,
@@ -17,6 +17,7 @@ export class HttpService implements HttpServiceInterface {
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string, @inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string, @inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: 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.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface, @inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Logger) private logger: Logger, @inject(TYPES.Logger) private logger: Logger,
@@ -58,6 +59,15 @@ export class HttpService implements HttpServiceInterface {
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload) 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( async callPaymentsServer(
request: Request, request: Request,
response: Response, response: Response,
@@ -37,4 +37,10 @@ export interface HttpServiceInterface {
endpoint: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> ): Promise<void>
callWebSocketServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
} }
-1
View File
@@ -67,7 +67,6 @@ VALET_TOKEN_SECRET=
VALET_TOKEN_TTL= VALET_TOKEN_TTL=
WEB_SOCKET_CONNECTION_TOKEN_SECRET= WEB_SOCKET_CONNECTION_TOKEN_SECRET=
WEB_SOCKET_CONNECTION_TOKEN_TTL=
# (Optional) Analytics # (Optional) Analytics
ANALYTICS_ENABLED=false ANALYTICS_ENABLED=false
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.45.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.44.0...@standardnotes/auth-server@1.45.0) (2022-10-13)
### Features
* publish workspace invite accepted event for websockets ([86379eb](https://github.com/standardnotes/server/commit/86379eb96d7231d6a76ee91350accef2d44a941d))
# [1.44.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.43.1...@standardnotes/auth-server@1.44.0) (2022-10-13)
### Features
* **auth:** remove websocket handling in favor of websockets service ([86ae4a5](https://github.com/standardnotes/server/commit/86ae4a59a3ac7915ad96ed5176b545f4d005e837))
## [1.43.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.43.0...@standardnotes/auth-server@1.43.1) (2022-10-13) ## [1.43.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.43.0...@standardnotes/auth-server@1.43.1) (2022-10-13)
**Note:** Version bump only for package @standardnotes/auth-server **Note:** Version bump only for package @standardnotes/auth-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.43.1", "version": "1.45.0",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
-25
View File
@@ -79,10 +79,6 @@ import { DeleteAccount } from '../Domain/UseCase/DeleteAccount/DeleteAccount'
import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting' import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting'
import { SettingFactory } from '../Domain/Setting/SettingFactory' import { SettingFactory } from '../Domain/Setting/SettingFactory'
import { SettingService } from '../Domain/Setting/SettingService' 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 axios, { AxiosInstance } from 'axios'
import { UserSubscription } from '../Domain/Subscription/UserSubscription' import { UserSubscription } from '../Domain/Subscription/UserSubscription'
import { MySQLUserSubscriptionRepository } from '../Infra/MySQL/MySQLUserSubscriptionRepository' import { MySQLUserSubscriptionRepository } from '../Infra/MySQL/MySQLUserSubscriptionRepository'
@@ -206,9 +202,6 @@ import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventH
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler' import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler' import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController' 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' import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -273,7 +266,6 @@ export class ContainerConfigLoader {
// Controller // Controller
container.bind<AuthController>(TYPES.AuthController).to(AuthController) container.bind<AuthController>(TYPES.AuthController).to(AuthController)
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController) container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
// Repositories // Repositories
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository) container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
@@ -295,9 +287,6 @@ export class ContainerConfigLoader {
.bind<RedisEphemeralSessionRepository>(TYPES.EphemeralSessionRepository) .bind<RedisEphemeralSessionRepository>(TYPES.EphemeralSessionRepository)
.to(RedisEphemeralSessionRepository) .to(RedisEphemeralSessionRepository)
container.bind<LockRepository>(TYPES.LockRepository).to(LockRepository) container.bind<LockRepository>(TYPES.LockRepository).to(LockRepository)
container
.bind<WebSocketsConnectionRepositoryInterface>(TYPES.WebSocketsConnectionRepository)
.to(RedisWebSocketsConnectionRepository)
container container
.bind<SubscriptionTokenRepositoryInterface>(TYPES.SubscriptionTokenRepository) .bind<SubscriptionTokenRepositoryInterface>(TYPES.SubscriptionTokenRepository)
.to(RedisSubscriptionTokenRepository) .to(RedisSubscriptionTokenRepository)
@@ -375,9 +364,6 @@ export class ContainerConfigLoader {
container container
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET) .bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true)) .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.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE')) container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_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.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.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.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.VERSION).toConstantValue(env.get('VERSION'))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true)) 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<UpdateSetting>(TYPES.UpdateSetting).to(UpdateSetting)
container.bind<DeleteSetting>(TYPES.DeleteSetting).to(DeleteSetting) container.bind<DeleteSetting>(TYPES.DeleteSetting).to(DeleteSetting)
container.bind<DeleteAccount>(TYPES.DeleteAccount).to(DeleteAccount) 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<GetUserSubscription>(TYPES.GetUserSubscription).to(GetUserSubscription)
container.bind<GetUserOfflineSubscription>(TYPES.GetUserOfflineSubscription).to(GetUserOfflineSubscription) container.bind<GetUserOfflineSubscription>(TYPES.GetUserOfflineSubscription).to(GetUserOfflineSubscription)
container.bind<CreateSubscriptionToken>(TYPES.CreateSubscriptionToken).to(CreateSubscriptionToken) container.bind<CreateSubscriptionToken>(TYPES.CreateSubscriptionToken).to(CreateSubscriptionToken)
@@ -454,9 +437,6 @@ export class ContainerConfigLoader {
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting) container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId) container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate) container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
container
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
.to(CreateWebSocketConnectionToken)
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken) container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
// Handlers // Handlers
@@ -547,11 +527,6 @@ export class ContainerConfigLoader {
container container
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder) .bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET))) .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<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory) container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create()) container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
-7
View File
@@ -6,7 +6,6 @@ const TYPES = {
// Controller // Controller
AuthController: Symbol.for('AuthController'), AuthController: Symbol.for('AuthController'),
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'), SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
WebSocketsController: Symbol.for('WebSocketsController'),
// Repositories // Repositories
UserRepository: Symbol.for('UserRepository'), UserRepository: Symbol.for('UserRepository'),
SessionRepository: Symbol.for('SessionRepository'), SessionRepository: Symbol.for('SessionRepository'),
@@ -17,7 +16,6 @@ const TYPES = {
OfflineSettingRepository: Symbol.for('OfflineSettingRepository'), OfflineSettingRepository: Symbol.for('OfflineSettingRepository'),
LockRepository: Symbol.for('LockRepository'), LockRepository: Symbol.for('LockRepository'),
RoleRepository: Symbol.for('RoleRepository'), RoleRepository: Symbol.for('RoleRepository'),
WebSocketsConnectionRepository: Symbol.for('WebSocketsConnectionRepository'),
UserSubscriptionRepository: Symbol.for('UserSubscriptionRepository'), UserSubscriptionRepository: Symbol.for('UserSubscriptionRepository'),
OfflineUserSubscriptionRepository: Symbol.for('OfflineUserSubscriptionRepository'), OfflineUserSubscriptionRepository: Symbol.for('OfflineUserSubscriptionRepository'),
SubscriptionTokenRepository: Symbol.for('SubscriptionTokenRepository'), SubscriptionTokenRepository: Symbol.for('SubscriptionTokenRepository'),
@@ -83,7 +81,6 @@ const TYPES = {
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'), REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'), NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
SYNCING_SERVER_URL: Symbol.for('SYNCING_SERVER_URL'), SYNCING_SERVER_URL: Symbol.for('SYNCING_SERVER_URL'),
WEBSOCKETS_API_URL: Symbol.for('WEBSOCKETS_API_URL'),
VERSION: Symbol.for('VERSION'), VERSION: Symbol.for('VERSION'),
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'), PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
// use cases // use cases
@@ -107,8 +104,6 @@ const TYPES = {
UpdateSetting: Symbol.for('UpdateSetting'), UpdateSetting: Symbol.for('UpdateSetting'),
DeleteSetting: Symbol.for('DeleteSetting'), DeleteSetting: Symbol.for('DeleteSetting'),
DeleteAccount: Symbol.for('DeleteAccount'), DeleteAccount: Symbol.for('DeleteAccount'),
AddWebSocketsConnection: Symbol.for('AddWebSocketsConnection'),
RemoveWebSocketsConnection: Symbol.for('RemoveWebSocketsConnection'),
GetUserSubscription: Symbol.for('GetUserSubscription'), GetUserSubscription: Symbol.for('GetUserSubscription'),
GetUserOfflineSubscription: Symbol.for('GetUserOfflineSubscription'), GetUserOfflineSubscription: Symbol.for('GetUserOfflineSubscription'),
CreateSubscriptionToken: Symbol.for('CreateSubscriptionToken'), CreateSubscriptionToken: Symbol.for('CreateSubscriptionToken'),
@@ -125,7 +120,6 @@ const TYPES = {
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'), GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'), GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
VerifyPredicate: Symbol.for('VerifyPredicate'), VerifyPredicate: Symbol.for('VerifyPredicate'),
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'), CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'),
// Handlers // Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'), UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
@@ -168,7 +162,6 @@ const TYPES = {
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'), CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'), SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'), ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
WebSocketConnectionTokenDecoder: Symbol.for('WebSocketConnectionTokenDecoder'), WebSocketConnectionTokenDecoder: Symbol.for('WebSocketConnectionTokenDecoder'),
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'), AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'), 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)) 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', () => { it('should create a EMAIL_MESSAGE_REQUESTED event', () => {
expect( expect(
createFactory().createEmailMessageRequestedEvent({ createFactory().createEmailMessageRequestedEvent({
@@ -1,4 +1,4 @@
import { EmailMessageIdentifier, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common' import { EmailMessageIdentifier, JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
import { import {
AccountDeletionRequestedEvent, AccountDeletionRequestedEvent,
UserEmailChangedEvent, UserEmailChangedEvent,
@@ -15,6 +15,7 @@ import {
PredicateVerifiedEvent, PredicateVerifiedEvent,
DomainEventService, DomainEventService,
EmailMessageRequestedEvent, EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
} from '@standardnotes/domain-events' } from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates' import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
@@ -27,6 +28,21 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface { export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} 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: { createEmailMessageRequestedEvent(dto: {
userEmail: string userEmail: string
messageIdentifier: EmailMessageIdentifier 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 { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { import {
AccountDeletionRequestedEvent, AccountDeletionRequestedEvent,
@@ -15,10 +15,12 @@ import {
SharedSubscriptionInvitationCanceledEvent, SharedSubscriptionInvitationCanceledEvent,
PredicateVerifiedEvent, PredicateVerifiedEvent,
EmailMessageRequestedEvent, EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
} from '@standardnotes/domain-events' } from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType' import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
export interface DomainEventFactoryInterface { export interface DomainEventFactoryInterface {
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
createEmailMessageRequestedEvent(dto: { createEmailMessageRequestedEvent(dto: {
userEmail: string userEmail: string
messageIdentifier: EmailMessageIdentifier 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 { ErrorTag } from '@standardnotes/common'
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security' import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
import { Request, Response } from 'express' import { Request } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { import {
BaseHttpController, BaseHttpController,
controller, controller,
httpDelete,
httpPost, httpPost,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
results, results,
} from 'inversify-express-utils' } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types' import TYPES from '../../Bootstrap/Types'
import { AddWebSocketsConnection } from '../../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { RemoveWebSocketsConnection } from '../../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
@controller('/sockets') @controller('/sockets')
export class InversifyExpressWebSocketsController extends BaseHttpController { export class InversifyExpressWebSocketsController extends BaseHttpController {
constructor( constructor(
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
@inject(TYPES.CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken, @inject(TYPES.CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken,
@inject(TYPES.WebSocketsController) private webSocketsController: WebSocketServerInterface,
@inject(TYPES.WebSocketConnectionTokenDecoder) @inject(TYPES.WebSocketConnectionTokenDecoder)
private tokenDecoder: TokenDecoderInterface<WebSocketConnectionTokenData>, private tokenDecoder: TokenDecoderInterface<WebSocketConnectionTokenData>,
) { ) {
super() 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') @httpPost('/tokens/validate')
async validateToken(request: Request): Promise<results.JsonResult> { async validateToken(request: Request): Promise<results.JsonResult> {
if (!request.headers.authorization) { if (!request.headers.authorization) {
@@ -72,26 +56,4 @@ export class InversifyExpressWebSocketsController extends BaseHttpController {
return this.json({ authToken: result.token }) 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,25 @@
import 'reflect-metadata' import 'reflect-metadata'
import { UserRolesChangedEvent } from '@standardnotes/domain-events' import {
DomainEventPublisherInterface,
UserRolesChangedEvent,
WebSocketMessageRequestedEvent,
} from '@standardnotes/domain-events'
import { RoleName } from '@standardnotes/common' import { RoleName } from '@standardnotes/common'
import { User } from '../../Domain/User/User' import { User } from '../../Domain/User/User'
import { WebSocketsClientService } from './WebSocketsClientService' import { WebSocketsClientService } from './WebSocketsClientService'
import { WebSocketsConnectionRepositoryInterface } from '../../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface'
import { AxiosInstance } from 'axios'
import { Logger } from 'winston'
describe('WebSocketsClientService', () => { describe('WebSocketsClientService', () => {
let connectionIds: string[]
let user: User let user: User
let event: UserRolesChangedEvent let event: UserRolesChangedEvent
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
let domainEventFactory: DomainEventFactoryInterface let domainEventFactory: DomainEventFactoryInterface
let httpClient: AxiosInstance let domainEventPublisher: DomainEventPublisherInterface
let logger: Logger
let webSocketsApiUrl = 'http://test-websockets' const createService = () => new WebSocketsClientService(domainEventFactory, domainEventPublisher)
const createService = () =>
new WebSocketsClientService(
webSocketsConnectionRepository,
domainEventFactory,
httpClient,
webSocketsApiUrl,
logger,
)
beforeEach(() => { beforeEach(() => {
connectionIds = ['1', '2']
user = { user = {
uuid: '123', uuid: '123',
email: 'test@test.com', email: 'test@test.com',
@@ -45,43 +32,22 @@ describe('WebSocketsClientService', () => {
event = {} as jest.Mocked<UserRolesChangedEvent> event = {} as jest.Mocked<UserRolesChangedEvent>
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
webSocketsConnectionRepository.findAllByUserUuid = jest.fn().mockReturnValue(connectionIds)
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface> domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createUserRolesChangedEvent = jest.fn().mockReturnValue(event) domainEventFactory.createUserRolesChangedEvent = jest.fn().mockReturnValue(event)
domainEventFactory.createWebSocketMessageRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
httpClient = {} as jest.Mocked<AxiosInstance> domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
httpClient.request = jest.fn() domainEventPublisher.publish = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = 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) await createService().sendUserRolesChangedEvent(user)
expect(domainEventFactory.createUserRolesChangedEvent).toHaveBeenCalledWith('123', 'test@test.com', [ expect(domainEventFactory.createUserRolesChangedEvent).toHaveBeenCalledWith('123', 'test@test.com', [
RoleName.ProUser, RoleName.ProUser,
]) ])
expect(httpClient.request).toHaveBeenCalledTimes(connectionIds.length) expect(domainEventPublisher.publish).toHaveBeenCalled()
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()
}) })
}) })
@@ -1,52 +1,31 @@
import { AxiosInstance } from 'axios'
import { RoleName } from '@standardnotes/common' import { RoleName } from '@standardnotes/common'
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types' import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface'
import { User } from '../../Domain/User/User' import { User } from '../../Domain/User/User'
import { WebSocketsConnectionRepositoryInterface } from '../../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface' import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
@injectable() @injectable()
export class WebSocketsClientService implements ClientServiceInterface { export class WebSocketsClientService implements ClientServiceInterface {
constructor( constructor(
@inject(TYPES.WebSocketsConnectionRepository)
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance, @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.WEBSOCKETS_API_URL) private webSocketsApiUrl: string,
@inject(TYPES.Logger) private logger: Logger,
) {} ) {}
async sendUserRolesChangedEvent(user: User): Promise<void> { 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( const event = this.domainEventFactory.createUserRolesChangedEvent(
user.uuid, user.uuid,
user.email, user.email,
(await user.roles).map((role) => role.name) as RoleName[], (await user.roles).map((role) => role.name) as RoleName[],
) )
for (const connectionUuid of userConnections) { await this.domainEventPublisher.publish(
await this.httpClient.request({ this.domainEventFactory.createWebSocketMessageRequestedEvent({
method: 'POST', userUuid: user.uuid,
url: `${this.webSocketsApiUrl}/${connectionUuid}`, message: JSON.stringify(event),
headers: { }),
Accept: 'text/plain', )
'Content-Type': 'text/plain',
},
data: JSON.stringify(event),
validateStatus:
/* istanbul ignore next */
(status: number) => status >= 200 && status < 500,
})
}
} }
} }
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.27](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.26...@standardnotes/domain-events-infra@1.8.27) (2022-10-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.26](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.25...@standardnotes/domain-events-infra@1.8.26) (2022-10-13) ## [1.8.26](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.25...@standardnotes/domain-events-infra@1.8.26) (2022-10-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra **Note:** Version bump only for package @standardnotes/domain-events-infra
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events-infra", "name": "@standardnotes/domain-events-infra",
"version": "1.8.26", "version": "1.8.27",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.68.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.67.0...@standardnotes/domain-events@2.68.0) (2022-10-13)
### Features
* publish workspace invite accepted event for websockets ([86379eb](https://github.com/standardnotes/server/commit/86379eb96d7231d6a76ee91350accef2d44a941d))
# [2.67.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.66.3...@standardnotes/domain-events@2.67.0) (2022-10-13) # [2.67.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.66.3...@standardnotes/domain-events@2.67.0) (2022-10-13)
### Features ### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events", "name": "@standardnotes/domain-events",
"version": "2.67.0", "version": "2.68.0",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { WorkspaceInviteAcceptedEventPayload } from './WorkspaceInviteAcceptedEventPayload'
export interface WorkspaceInviteAcceptedEvent extends DomainEventInterface {
type: 'WORKSPACE_INVITE_ACCEPTED'
payload: WorkspaceInviteAcceptedEventPayload
}
@@ -0,0 +1,5 @@
export interface WorkspaceInviteAcceptedEventPayload {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}
@@ -102,6 +102,8 @@ export * from './Event/UserSignedInEvent'
export * from './Event/UserSignedInEventPayload' export * from './Event/UserSignedInEventPayload'
export * from './Event/WebSocketMessageRequestedEvent' export * from './Event/WebSocketMessageRequestedEvent'
export * from './Event/WebSocketMessageRequestedEventPayload' export * from './Event/WebSocketMessageRequestedEventPayload'
export * from './Event/WorkspaceInviteAcceptedEvent'
export * from './Event/WorkspaceInviteAcceptedEventPayload'
export * from './Event/WorkspaceInviteCreatedEvent' export * from './Event/WorkspaceInviteCreatedEvent'
export * from './Event/WorkspaceInviteCreatedEventPayload' export * from './Event/WorkspaceInviteCreatedEventPayload'
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.5...@standardnotes/event-store@1.4.6) (2022-10-13)
**Note:** Version bump only for package @standardnotes/event-store
## [1.4.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.4...@standardnotes/event-store@1.4.5) (2022-10-13) ## [1.4.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.4.4...@standardnotes/event-store@1.4.5) (2022-10-13)
**Note:** Version bump only for package @standardnotes/event-store **Note:** Version bump only for package @standardnotes/event-store
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/event-store", "name": "@standardnotes/event-store",
"version": "1.4.5", "version": "1.4.6",
"description": "Event Store Service", "description": "Event Store Service",
"private": true, "private": true,
"main": "dist/src/index.js", "main": "dist/src/index.js",
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.18](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.17...@standardnotes/files-server@1.6.18) (2022-10-13)
**Note:** Version bump only for package @standardnotes/files-server
## [1.6.17](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.16...@standardnotes/files-server@1.6.17) (2022-10-13) ## [1.6.17](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.6.16...@standardnotes/files-server@1.6.17) (2022-10-13)
**Note:** Version bump only for package @standardnotes/files-server **Note:** Version bump only for package @standardnotes/files-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/files-server", "name": "@standardnotes/files-server",
"version": "1.6.17", "version": "1.6.18",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.46](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.45...@standardnotes/scheduler-server@1.10.46) (2022-10-13)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.45](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.44...@standardnotes/scheduler-server@1.10.45) (2022-10-13) ## [1.10.45](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.44...@standardnotes/scheduler-server@1.10.45) (2022-10-13)
**Note:** Version bump only for package @standardnotes/scheduler-server **Note:** Version bump only for package @standardnotes/scheduler-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/scheduler-server", "name": "@standardnotes/scheduler-server",
"version": "1.10.45", "version": "1.10.46",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.7...@standardnotes/syncing-server@1.9.8) (2022-10-13)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.9.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.6...@standardnotes/syncing-server@1.9.7) (2022-10-13) ## [1.9.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.9.6...@standardnotes/syncing-server@1.9.7) (2022-10-13)
**Note:** Version bump only for package @standardnotes/syncing-server **Note:** Version bump only for package @standardnotes/syncing-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/syncing-server", "name": "@standardnotes/syncing-server",
"version": "1.9.7", "version": "1.9.8",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.1.2...@standardnotes/websockets-server@1.1.3) (2022-10-13)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.1.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.1.1...@standardnotes/websockets-server@1.1.2) (2022-10-13)
### Bug Fixes
* **websockets:** add http client binding ([4e21edc](https://github.com/standardnotes/server/commit/4e21edce6b034312f121db4dce716e82ff7d5eaa))
## [1.1.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.1.0...@standardnotes/websockets-server@1.1.1) (2022-10-13)
### Bug Fixes
* **websockets:** remove unnecessary sns bindings ([2f7ef49](https://github.com/standardnotes/server/commit/2f7ef497ab4875685d6a0f282eeae11005900bc3))
# 1.1.0 (2022-10-13) # 1.1.0 (2022-10-13)
### Features ### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/websockets-server", "name": "@standardnotes/websockets-server",
"version": "1.1.0", "version": "1.1.3",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
+2 -11
View File
@@ -1,4 +1,5 @@
import * as winston from 'winston' import * as winston from 'winston'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis' import Redis from 'ioredis'
import * as AWS from 'aws-sdk' import * as AWS from 'aws-sdk'
import { Container } from 'inversify' import { Container } from 'inversify'
@@ -71,15 +72,6 @@ export class ContainerConfigLoader {
}) })
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger) container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
if (env.get('SNS_AWS_REGION', true)) {
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
new AWS.SNS({
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}),
)
}
if (env.get('SQS_QUEUE_URL', true)) { if (env.get('SQS_QUEUE_URL', true)) {
const sqsConfig: AWS.SQS.Types.ClientConfiguration = { const sqsConfig: AWS.SQS.Types.ClientConfiguration = {
apiVersion: 'latest', apiVersion: 'latest',
@@ -114,8 +106,6 @@ export class ContainerConfigLoader {
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL) .bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true)) .toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL')) container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true)) container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL')) 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.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
@@ -135,6 +125,7 @@ export class ContainerConfigLoader {
.to(WebSocketMessageRequestedEventHandler) .to(WebSocketMessageRequestedEventHandler)
// Services // Services
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
container container
.bind<TokenDecoderInterface<CrossServiceTokenData>>(TYPES.CrossServiceTokenDecoder) .bind<TokenDecoderInterface<CrossServiceTokenData>>(TYPES.CrossServiceTokenDecoder)
.toConstantValue(new TokenDecoder<CrossServiceTokenData>(container.get(TYPES.AUTH_JWT_SECRET))) .toConstantValue(new TokenDecoder<CrossServiceTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
@@ -1,7 +1,6 @@
const TYPES = { const TYPES = {
Logger: Symbol.for('Logger'), Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'), Redis: Symbol.for('Redis'),
SNS: Symbol.for('SNS'),
SQS: Symbol.for('SQS'), SQS: Symbol.for('SQS'),
// Controller // Controller
WebSocketsController: Symbol.for('WebSocketsController'), WebSocketsController: Symbol.for('WebSocketsController'),
@@ -14,8 +13,6 @@ const TYPES = {
WEB_SOCKET_CONNECTION_TOKEN_SECRET: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_SECRET'), WEB_SOCKET_CONNECTION_TOKEN_SECRET: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_SECRET'),
WEB_SOCKET_CONNECTION_TOKEN_TTL: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_TTL'), WEB_SOCKET_CONNECTION_TOKEN_TTL: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_TTL'),
REDIS_URL: Symbol.for('REDIS_URL'), REDIS_URL: Symbol.for('REDIS_URL'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'), SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'), SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'), REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.2...@standardnotes/workspace-server@1.14.0) (2022-10-13)
### Features
* publish workspace invite accepted event for websockets ([86379eb](https://github.com/standardnotes/server/commit/86379eb96d7231d6a76ee91350accef2d44a941d))
## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.1...@standardnotes/workspace-server@1.13.2) (2022-10-13) ## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.13.1...@standardnotes/workspace-server@1.13.2) (2022-10-13)
**Note:** Version bump only for package @standardnotes/workspace-server **Note:** Version bump only for package @standardnotes/workspace-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/workspace-server", "name": "@standardnotes/workspace-server",
"version": "1.13.2", "version": "1.14.0",
"engines": { "engines": {
"node": ">=16.0.0 <17.0.0" "node": ">=16.0.0 <17.0.0"
}, },
@@ -15,6 +15,54 @@ describe('DomainEventFactory', () => {
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1)) 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: 'workspace',
},
payload: {
userUuid: '1-2-3',
message: 'foobar',
},
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
})
})
it('should create a WORKSPACE_INVITE_ACCEPTED event', () => {
expect(
createFactory().createWorkspaceInviteAcceptedEvent({
inviterUuid: '1-2-3',
inviteeUuid: '2-3-4',
workspaceUuid: 'w-1-2-3',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '2-3-4',
userIdentifierType: 'uuid',
},
origin: 'workspace',
},
payload: {
inviterUuid: '1-2-3',
inviteeUuid: '2-3-4',
workspaceUuid: 'w-1-2-3',
},
type: 'WORKSPACE_INVITE_ACCEPTED',
})
})
it('should create a WORKSPACE_INVITE_CREATED event', () => { it('should create a WORKSPACE_INVITE_CREATED event', () => {
expect( expect(
createFactory().createWorkspaceInviteCreatedEvent({ createFactory().createWorkspaceInviteCreatedEvent({
@@ -1,4 +1,9 @@
import { DomainEventService, WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events' import {
DomainEventService,
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
WorkspaceInviteCreatedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
@@ -10,6 +15,40 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface { export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createWorkspaceInviteAcceptedEvent(dto: {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}): WorkspaceInviteAcceptedEvent {
return {
type: 'WORKSPACE_INVITE_ACCEPTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.inviteeUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Workspace,
},
payload: dto,
}
}
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent {
return {
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Workspace,
},
payload: dto,
}
}
createWorkspaceInviteCreatedEvent(dto: { createWorkspaceInviteCreatedEvent(dto: {
inviterUuid: string inviterUuid: string
inviteeEmail: string inviteeEmail: string
@@ -1,4 +1,9 @@
import { WorkspaceInviteCreatedEvent } from '@standardnotes/domain-events' import { JSONString } from '@standardnotes/common'
import {
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
WorkspaceInviteCreatedEvent,
} from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface { export interface DomainEventFactoryInterface {
createWorkspaceInviteCreatedEvent(dto: { createWorkspaceInviteCreatedEvent(dto: {
@@ -7,4 +12,10 @@ export interface DomainEventFactoryInterface {
inviteUuid: string inviteUuid: string
workspaceUuid: string workspaceUuid: string
}): WorkspaceInviteCreatedEvent }): WorkspaceInviteCreatedEvent
createWorkspaceInviteAcceptedEvent(dto: {
inviterUuid: string
inviteeUuid: string
workspaceUuid: string
}): WorkspaceInviteAcceptedEvent
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: JSONString }): WebSocketMessageRequestedEvent
} }
@@ -7,14 +7,29 @@ import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserR
import { AcceptInvitation } from './AcceptInvitation' import { AcceptInvitation } from './AcceptInvitation'
import { WorkspaceAccessLevel } from '@standardnotes/common' import { WorkspaceAccessLevel } from '@standardnotes/common'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import {
DomainEventPublisherInterface,
WebSocketMessageRequestedEvent,
WorkspaceInviteAcceptedEvent,
} from '@standardnotes/domain-events'
describe('AcceptInvitation', () => { describe('AcceptInvitation', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface let workspaceUserRepository: WorkspaceUserRepositoryInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let timer: TimerInterface let timer: TimerInterface
let invite: WorkspaceInvite let invite: WorkspaceInvite
const createUseCase = () => new AcceptInvitation(workspaceInviteRepository, workspaceUserRepository, timer) const createUseCase = () =>
new AcceptInvitation(
workspaceInviteRepository,
workspaceUserRepository,
domainEventFactory,
domainEventPublisher,
timer,
)
beforeEach(() => { beforeEach(() => {
invite = { invite = {
@@ -32,6 +47,17 @@ describe('AcceptInvitation', () => {
timer = {} as jest.Mocked<TimerInterface> timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createWebSocketMessageRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
domainEventFactory.createWorkspaceInviteAcceptedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WorkspaceInviteAcceptedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
}) })
it('should accept an invite and assign user to workspace', async () => { it('should accept an invite and assign user to workspace', async () => {
@@ -11,12 +11,16 @@ import { UseCaseInterface } from '../UseCaseInterface'
import { AcceptInvitationDTO } from './AcceptInvitationDTO' import { AcceptInvitationDTO } from './AcceptInvitationDTO'
import { AcceptInvitationResponse } from './AcceptInvitationResponse' import { AcceptInvitationResponse } from './AcceptInvitationResponse'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
@injectable() @injectable()
export class AcceptInvitation implements UseCaseInterface { export class AcceptInvitation implements UseCaseInterface {
constructor( constructor(
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface, @inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface, @inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Timer) private timer: TimerInterface, @inject(TYPES.Timer) private timer: TimerInterface,
) {} ) {}
@@ -45,6 +49,19 @@ export class AcceptInvitation implements UseCaseInterface {
await this.workspaceUserRepository.save(workspaceUser) await this.workspaceUserRepository.save(workspaceUser)
const event = this.domainEventFactory.createWorkspaceInviteAcceptedEvent({
inviteeUuid: invite.acceptingUserUuid,
inviterUuid: invite.inviterUuid,
workspaceUuid: invite.workspaceUuid,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createWebSocketMessageRequestedEvent({
userUuid: invite.inviterUuid,
message: JSON.stringify(event),
}),
)
return { return {
success: true, success: true,
} }