feat(api): add websocket api definitions

This commit is contained in:
Karol Sójko
2022-09-20 13:20:52 +02:00
parent dd5ca0c28c
commit 4a773fa537
18 changed files with 190 additions and 5 deletions

View File

@@ -10,10 +10,10 @@ module.exports = {
},
coverageThreshold: {
global: {
branches: 17,
functions: 43,
lines: 46,
statements: 46
branches: 20,
functions: 66,
lines: 63,
statements: 63
}
}
};

View File

@@ -32,7 +32,8 @@
"eslint": "^8.23.0",
"eslint-plugin-prettier": "*",
"jest": "^28.1.2",
"ts-jest": "^28.0.5"
"ts-jest": "^28.0.5",
"typescript": "*"
},
"dependencies": {
"@standardnotes/common": "^1.32.0",

View File

@@ -0,0 +1,3 @@
export enum WebSocketApiOperations {
CreatingConnectionToken,
}

View File

@@ -0,0 +1,61 @@
import { WebSocketConnectionTokenResponse } from '../../Response'
import { WebSocketServerInterface } from '../../Server/WebSocket/WebSocketServerInterface'
import { WebSocketApiOperations } from './WebSocketApiOperations'
import { WebSocketApiService } from './WebSocketApiService'
describe('WebSocketApiService', () => {
let webSocketServer: WebSocketServerInterface
const createService = () => new WebSocketApiService(webSocketServer)
beforeEach(() => {
webSocketServer = {} as jest.Mocked<WebSocketServerInterface>
webSocketServer.createConnectionToken = jest.fn().mockReturnValue({
data: { token: 'foobar' },
} as jest.Mocked<WebSocketConnectionTokenResponse>)
})
it('should create a websocket connection token', async () => {
const response = await createService().createConnectionToken()
expect(response).toEqual({
data: {
token: 'foobar',
},
})
expect(webSocketServer.createConnectionToken).toHaveBeenCalledWith({})
})
it('should not create a token if it is already creating', async () => {
const service = createService()
Object.defineProperty(service, 'operationsInProgress', {
get: () => new Map([[WebSocketApiOperations.CreatingConnectionToken, true]]),
})
let error = null
try {
await service.createConnectionToken()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
it('should not create a token if the server fails', async () => {
webSocketServer.createConnectionToken = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
let error = null
try {
await createService().createConnectionToken()
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})

View File

@@ -0,0 +1,33 @@
import { ErrorMessage } from '../../Error/ErrorMessage'
import { ApiCallError } from '../../Error/ApiCallError'
import { WebSocketApiServiceInterface } from './WebSocketApiServiceInterface'
import { WebSocketApiOperations } from './WebSocketApiOperations'
import { WebSocketServerInterface } from '../../Server'
import { WebSocketConnectionTokenResponse } from '../../Response'
export class WebSocketApiService implements WebSocketApiServiceInterface {
private operationsInProgress: Map<WebSocketApiOperations, boolean>
constructor(private webSocketServer: WebSocketServerInterface) {
this.operationsInProgress = new Map()
}
async createConnectionToken(): Promise<WebSocketConnectionTokenResponse> {
if (this.operationsInProgress.get(WebSocketApiOperations.CreatingConnectionToken)) {
throw new ApiCallError(ErrorMessage.GenericInProgress)
}
this.operationsInProgress.set(WebSocketApiOperations.CreatingConnectionToken, true)
try {
const response = await this.webSocketServer.createConnectionToken({})
this.operationsInProgress.set(WebSocketApiOperations.CreatingConnectionToken, false)
return response
} catch (error) {
throw new ApiCallError(ErrorMessage.GenericFail)
}
}
}

View File

@@ -0,0 +1,5 @@
import { WebSocketConnectionTokenResponse } from '../../Response'
export interface WebSocketApiServiceInterface {
createConnectionToken(): Promise<WebSocketConnectionTokenResponse>
}

View File

@@ -3,3 +3,5 @@ export * from './Subscription/SubscriptionApiService'
export * from './Subscription/SubscriptionApiServiceInterface'
export * from './User/UserApiService'
export * from './User/UserApiServiceInterface'
export * from './WebSocket/WebSocketApiService'
export * from './WebSocket/WebSocketApiServiceInterface'

View File

@@ -0,0 +1,3 @@
export type WebSocketConnectionTokenRequestParams = {
[additionalParam: string]: unknown
}

View File

@@ -5,3 +5,4 @@ export * from './Subscription/SubscriptionInviteDeclineRequestParams'
export * from './Subscription/SubscriptionInviteListRequestParams'
export * from './Subscription/SubscriptionInviteRequestParams'
export * from './User/UserRegistrationRequestParams'
export * from './WebSocket/WebSocketConnectionTokenRequestParams'

View File

@@ -0,0 +1,9 @@
import { Either } from '@standardnotes/common'
import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody'
import { HttpResponse } from '../../Http/HttpResponse'
import { WebSocketConnectionTokenResponseBody } from './WebSocketConnectionTokenResponseBody'
export interface WebSocketConnectionTokenResponse extends HttpResponse {
data: Either<WebSocketConnectionTokenResponseBody, HttpErrorResponseBody>
}

View File

@@ -0,0 +1,3 @@
export type WebSocketConnectionTokenResponseBody = {
token: string
}

View File

@@ -10,3 +10,5 @@ export * from './Subscription/SubscriptionInviteResponse'
export * from './Subscription/SubscriptionInviteResponseBody'
export * from './User/UserRegistrationResponse'
export * from './User/UserRegistrationResponseBody'
export * from './WebSocket/WebSocketConnectionTokenResponse'
export * from './WebSocket/WebSocketConnectionTokenResponseBody'

View File

@@ -0,0 +1,9 @@
const TokenPaths = {
createConnectionToken: '/v1/sockets/tokens',
}
export const Paths = {
v1: {
...TokenPaths,
},
}

View File

@@ -0,0 +1,27 @@
import { HttpServiceInterface } from '../../Http'
import { WebSocketConnectionTokenResponse } from '../../Response'
import { WebSocketServer } from './WebSocketServer'
describe('WebSocketServer', () => {
let httpService: HttpServiceInterface
const createServer = () => new WebSocketServer(httpService)
beforeEach(() => {
httpService = {} as jest.Mocked<HttpServiceInterface>
httpService.post = jest.fn().mockReturnValue({
data: { token: 'foobar' },
} as jest.Mocked<WebSocketConnectionTokenResponse>)
})
it('should create a websocket connection token', async () => {
const response = await createServer().createConnectionToken({})
expect(response).toEqual({
data: {
token: 'foobar',
},
})
})
})

View File

@@ -0,0 +1,17 @@
import { HttpServiceInterface } from '../../Http/HttpServiceInterface'
import { WebSocketConnectionTokenRequestParams } from '../../Request/WebSocket/WebSocketConnectionTokenRequestParams'
import { WebSocketConnectionTokenResponse } from '../../Response/WebSocket/WebSocketConnectionTokenResponse'
import { Paths } from './Paths'
import { WebSocketServerInterface } from './WebSocketServerInterface'
export class WebSocketServer implements WebSocketServerInterface {
constructor(private httpService: HttpServiceInterface) {}
async createConnectionToken(
params: WebSocketConnectionTokenRequestParams,
): Promise<WebSocketConnectionTokenResponse> {
const response = await this.httpService.post(Paths.v1.createConnectionToken, params)
return response as WebSocketConnectionTokenResponse
}
}

View File

@@ -0,0 +1,6 @@
import { WebSocketConnectionTokenRequestParams } from '../../Request/WebSocket/WebSocketConnectionTokenRequestParams'
import { WebSocketConnectionTokenResponse } from '../../Response/WebSocket/WebSocketConnectionTokenResponse'
export interface WebSocketServerInterface {
createConnectionToken(params: WebSocketConnectionTokenRequestParams): Promise<WebSocketConnectionTokenResponse>
}

View File

@@ -2,3 +2,5 @@ export * from './Subscription/SubscriptionServer'
export * from './Subscription/SubscriptionServerInterface'
export * from './User/UserServer'
export * from './User/UserServerInterface'
export * from './WebSocket/WebSocketServer'
export * from './WebSocket/WebSocketServerInterface'

View File

@@ -6397,6 +6397,7 @@ __metadata:
jest: ^28.1.2
reflect-metadata: ^0.1.13
ts-jest: ^28.0.5
typescript: "*"
languageName: unknown
linkType: soft