From 14b7a8cff0f2ff1874fd1a1ea2179318cac4ca0a Mon Sep 17 00:00:00 2001 From: Matvey Melishev Date: Sat, 10 Feb 2024 19:55:19 +0100 Subject: [PATCH] feat: add additional data to received message, move debugger to decorator (#15) * feat: add additional data to received message, move debugger to decorator * test: update subscribe test * test: update all tests * test: fix bug when transform data on mock server * refactor: move subscribe logic to folder --- src/helpers.ts | 14 ++++++++++++ src/index.ts | 14 ++---------- src/subscribe.ts | 25 -------------------- src/subscribe/index.ts | 52 ++++++++++++++++++++++++++++++++++++++++++ src/subscribe/types.ts | 12 ++++++++++ test/close.test.ts | 15 ++++-------- test/create.test.ts | 17 ++++++-------- test/heartbeat.test.ts | 4 ++-- test/open.test.ts | 17 ++++++-------- test/send.test.ts | 36 ++++++++++++++--------------- test/subscribe.test.ts | 35 ++++++++++++++-------------- test/utils.ts | 2 +- 12 files changed, 138 insertions(+), 105 deletions(-) create mode 100644 src/helpers.ts delete mode 100644 src/subscribe.ts create mode 100644 src/subscribe/index.ts create mode 100644 src/subscribe/types.ts diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..bd4e4d5 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,14 @@ +/** Envelopes the content in a string of a certain length */ +export function formatString(text: string, count: number): string { + const spaceString = ' '.repeat(count) + return text + spaceString.substring(text.length) +} + +/** Converts milliseconds into a conveniently readable unit */ +export function fromMsToUp(ms: number): { value: number; unit: 'ms' | 's' | 'm' | 'h' } { + if (ms < 1000) return { value: ms, unit: 'ms' } + if (ms < 60000) return { value: ms / 1000, unit: 's' } + if (ms < 3600000) return { value: ms / 60000, unit: 'm' } + + return { value: ms / 3600000, unit: 'h' } +} diff --git a/src/index.ts b/src/index.ts index 7c2d809..3685c94 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ import { type WSGOEventName, type WSGOConfig, type WSGOSubscriptions } from './types' -import { type WSGOSubscribeCallback } from './subscribe' - import { send } from './send' import { subscribe } from './subscribe' +import type { WSGOSubscribeCallback } from './subscribe/types' import { heartbeatStart, heartbeatStop, listenHeartbeat } from './heartbeat' /** Method allows you create new WebSocket connection */ @@ -63,7 +62,7 @@ export default function create( send(...args, ws, _config) }, subscribe: (...args) => { - subscribe(subscriptions, ...args) + subscribe(...args, subscriptions, _config) }, } } @@ -112,15 +111,6 @@ function _listen(ws: WebSocket, subscriptions: WSGOSubscriptions, _config: WSGOC return } - if (_config.debugging) { - if (message.event === 'exception') { - console.error(message.data) - } else { - const { event, data, time } = message - console.log(`%c${new Date(time).toLocaleTimeString()}%c`, 'color: gray', '', event, data) - } - } - if (message.event in subscriptions) { subscriptions[message.event](message) } diff --git a/src/subscribe.ts b/src/subscribe.ts deleted file mode 100644 index 54bc185..0000000 --- a/src/subscribe.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { type WSGOSubscriptions } from './types' - -export type WSGOSubscribeCallback = (message: WSGOSubscribeResponse) => any - -export interface WSGOSubscribeResponse { - /** Event name */ - event: string - /** Event data */ - data: T - /** Time when the server sent the event */ - timeSended: number - /** Time when the client received the event */ - timeReceived: number -} - -/** Method allows you to subscribe to listen to a specific event */ -export function subscribe( - subscriptions: WSGOSubscriptions, - eventName: string, - callback: WSGOSubscribeCallback, -): void { - if (eventName in subscriptions) return - - Object.assign(subscriptions, { [eventName]: callback }) -} diff --git a/src/subscribe/index.ts b/src/subscribe/index.ts new file mode 100644 index 0000000..0dfe182 --- /dev/null +++ b/src/subscribe/index.ts @@ -0,0 +1,52 @@ +import { formatString, fromMsToUp } from '../helpers' +import type { WSGOSubscribeResponse, WSGOSubscribeCallback } from './types' +import type { WSGOEventName, WSGOConfig, WSGOSubscriptions } from '../types' + +/** Method allows you to subscribe to listen to a specific event */ +export function subscribe( + eventName: WSGOEventName, + callback: WSGOSubscribeCallback, + subscriptions: WSGOSubscriptions, + _config: WSGOConfig, +): void { + if (eventName in subscriptions) return + + callback = _subscribeDecoratorTransform(callback, _config) + + Object.assign(subscriptions, { [eventName]: callback }) +} + +/** Decorator adds aditional data to received events */ +function _subscribeDecoratorTransform(callback: WSGOSubscribeCallback, _config: WSGOConfig): any { + return (message: Omit, 'timeReceived'>) => { + const transformedMessage: WSGOSubscribeResponse = { + ...message, + timeReceived: Date.now(), + } + + _subscribeDebugger(transformedMessage, _config) + callback(transformedMessage) + } +} + +function _subscribeDebugger(message: WSGOSubscribeResponse, _config: WSGOConfig): void { + if (!_config.debugging) return + + if (message.event === 'exception') { + console.error(message.data) + return + } + + const { event, data, timeSended, timeReceived } = message + const timeDiff = fromMsToUp(timeReceived - timeSended) + + // console.log(`%c${new Date(timeReceived).toLocaleTimeString()}%c`, 'color: gray', '', event, data) + // console.log(`%c${new Date(timeReceived).toLocaleTimeString()} %c${timeDiff}ms`, 'color: gray', 'color: green', event, data) + console.log( + `%c${new Date(timeReceived).toLocaleTimeString()} %c${formatString(`${timeDiff.value + timeDiff.unit}`, 5)}%c ${formatString(event, 30)}`, + 'color: gray', + 'color: green', + '', + data, + ) +} diff --git a/src/subscribe/types.ts b/src/subscribe/types.ts new file mode 100644 index 0000000..e704ae5 --- /dev/null +++ b/src/subscribe/types.ts @@ -0,0 +1,12 @@ +export interface WSGOSubscribeResponse { + /** Event name */ + event: string + /** Event data */ + data: T + /** Time when the server sent the event */ + timeSended: number + /** Time when the client received the event */ + timeReceived: number +} + +export type WSGOSubscribeCallback = (message: WSGOSubscribeResponse) => any diff --git a/test/close.test.ts b/test/close.test.ts index dfb928c..672ac0c 100644 --- a/test/close.test.ts +++ b/test/close.test.ts @@ -1,22 +1,17 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import WSGO from '../src/index' import ws from 'ws' +import { createMockWSServer } from './utils' describe('close', () => { - let port = 0 + let port: number = 0 let server: ws.Server beforeAll(() => { - server = new ws.WebSocketServer({ port }) + const mockWSServer = createMockWSServer(port) - server.on('connection', (ws) => { - ws.on('message', (data, isBinary) => { - const message = isBinary ? data : data.toString() - ws.send(message) - }) - }) - - port = (server.address() as ws.AddressInfo).port + server = mockWSServer.server + port = mockWSServer.port }) afterAll(() => { diff --git a/test/create.test.ts b/test/create.test.ts index 1d8fa98..eb099fa 100644 --- a/test/create.test.ts +++ b/test/create.test.ts @@ -1,22 +1,17 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import WSGO from '../src/index' import ws from 'ws' +import { createMockWSServer } from './utils' describe('create', () => { - let port = 0 + let port: number = 0 let server: ws.Server beforeAll(() => { - server = new ws.WebSocketServer({ port }) + const mockWSServer = createMockWSServer(port) - server.on('connection', (ws) => { - ws.on('message', (data, isBinary) => { - const message = isBinary ? data : data.toString() - ws.send(message) - }) - }) - - port = (server.address() as ws.AddressInfo).port + server = mockWSServer.server + port = mockWSServer.port }) afterAll(() => { @@ -52,4 +47,6 @@ describe('create', () => { // Assert expect(wsgo.ws).toBeUndefined() }) + + // TODO: write tests for options }) diff --git a/test/heartbeat.test.ts b/test/heartbeat.test.ts index c68e4cd..3e50db1 100644 --- a/test/heartbeat.test.ts +++ b/test/heartbeat.test.ts @@ -3,7 +3,7 @@ import WSGO from '../src/index' import ws from 'ws' import { createMockWSServer } from './utils' -describe('open', () => { +describe('heartbeat', () => { const date = new Date(2000, 1, 1) let port: number = 0 @@ -50,7 +50,7 @@ describe('open', () => { ) // Assert - expect(event).toStrictEqual({ event: eventName, timeSended: Date.now() }) + expect(event).toStrictEqual({ event: eventName, timeSended: Date.now(), timeReceived: Date.now() }) }) it.todo('must close the connection if no response is received from the server') diff --git a/test/open.test.ts b/test/open.test.ts index 3b63053..cd9dced 100644 --- a/test/open.test.ts +++ b/test/open.test.ts @@ -1,22 +1,17 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import WSGO from '../src/index' import ws from 'ws' +import { createMockWSServer } from './utils' describe('open', () => { - let port = 0 + let port: number = 0 let server: ws.Server beforeAll(() => { - server = new ws.WebSocketServer({ port }) + const mockWSServer = createMockWSServer(port) - server.on('connection', (ws) => { - ws.on('message', (data, isBinary) => { - const message = isBinary ? data : data.toString() - ws.send(message) - }) - }) - - port = (server.address() as ws.AddressInfo).port + server = mockWSServer.server + port = mockWSServer.port }) afterAll(() => { @@ -38,4 +33,6 @@ describe('open', () => { // check source data expect(wsgo.ws?.url).toBe(`ws://localhost:${port}/`) }) + + it.todo('should open new Websocket, after closing old', () => {}) }) diff --git a/test/send.test.ts b/test/send.test.ts index e85fb26..0eb5e5b 100644 --- a/test/send.test.ts +++ b/test/send.test.ts @@ -1,22 +1,19 @@ -import { describe, it, expect, vitest, beforeAll, afterAll } from 'vitest' +import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest' import WSGO from '../src/index' import ws from 'ws' +import { createMockWSServer } from './utils' describe('send', () => { - let port = 0 + const date = new Date(2000, 1, 1) + + let port: number = 0 let server: ws.Server beforeAll(() => { - server = new ws.WebSocketServer({ port }) - - server.on('connection', (ws) => { - ws.on('message', (data, isBinary) => { - const message = isBinary ? data : data.toString() - ws.send(message) - }) - }) + const mockWSServer = createMockWSServer(port) - port = (server.address() as ws.AddressInfo).port + server = mockWSServer.server + port = mockWSServer.port }) afterAll(() => { @@ -24,28 +21,31 @@ describe('send', () => { }) it('should send an event to the server', async () => { + const eventName = 'eventName' + const eventData = { text: 'Hello World!' } + let event: any // Arrange const wsgo = WSGO(`ws://localhost:${port}`) - await vitest.waitFor(() => { + await vi.waitFor(() => { + vi.setSystemTime(date) if (wsgo.ws?.readyState !== window.WebSocket.OPEN) { throw new Error() } }) // Act - wsgo.subscribe('eventName', (ev) => (event = ev)) - wsgo.send('eventName', { text: 'Hello World!' }) - await vitest.waitFor(() => { + wsgo.subscribe(eventName, (ev) => (event = ev)) + wsgo.send(eventName, eventData) + await vi.waitFor(() => { + vi.setSystemTime(date) if (event === undefined) { throw new Error('Message not received back') } }) // Assert - expect(wsgo.ws).toBeInstanceOf(window.WebSocket) - expect(wsgo.ws?.readyState).toBe(window.WebSocket.OPEN) - expect(event).toStrictEqual({ event: 'eventName', data: { text: 'Hello World!' } }) + expect(event).toStrictEqual({ event: eventName, data: eventData, timeSended: Date.now(), timeReceived: Date.now() }) }) }) diff --git a/test/subscribe.test.ts b/test/subscribe.test.ts index 12988d0..5288959 100644 --- a/test/subscribe.test.ts +++ b/test/subscribe.test.ts @@ -1,22 +1,19 @@ -import { describe, it, expect, vitest, beforeAll, afterAll } from 'vitest' +import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest' import WSGO from '../src/index' import ws from 'ws' +import { createMockWSServer } from './utils' describe('subscribe', () => { - let port = 0 + const date = new Date(2000, 1, 1) + + let port: number = 0 let server: ws.Server beforeAll(() => { - server = new ws.WebSocketServer({ port }) - - server.on('connection', (ws) => { - ws.on('message', (data, isBinary) => { - const message = isBinary ? data : data.toString() - ws.send(message) - }) - }) + const mockWSServer = createMockWSServer(port) - port = (server.address() as ws.AddressInfo).port + server = mockWSServer.server + port = mockWSServer.port }) afterAll(() => { @@ -24,28 +21,32 @@ describe('subscribe', () => { }) it('should subscribe to event', async () => { + const eventName = 'eventName' + const eventData = { text: 'Hello World!' } + let event: any // Arrange const wsgo = WSGO(`ws://localhost:${port}`) - await vitest.waitFor(() => { + await vi.waitFor(() => { + vi.setSystemTime(date) if (wsgo.ws?.readyState !== window.WebSocket.OPEN) { throw new Error() } }) // Act - wsgo.subscribe('eventName', (ev) => (event = ev)) - wsgo.send('eventName', { text: 'Hello World!' }) - await vitest.waitFor(() => { + wsgo.subscribe(eventName, (ev) => (event = ev)) + wsgo.send(eventName, eventData) + await vi.waitFor(() => { + vi.setSystemTime(date) if (event === undefined) { throw new Error() } }) - wsgo.close() // Assert - expect(event).toStrictEqual({ event: 'eventName', data: { text: 'Hello World!' } }) + expect(event).toStrictEqual({ event: eventName, data: eventData, timeSended: Date.now(), timeReceived: Date.now() }) }) it.todo('should work once', () => {}) diff --git a/test/utils.ts b/test/utils.ts index b42d547..09de95f 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -12,7 +12,7 @@ export function createMockWSServer(port: number = 0): { server: ws.WebSocketServ ws.send(JSON.stringify(message)) } - const message = { ...data, timeSended: Date.now() } + const message = { ...parsedData, timeSended: Date.now() } // setTimeout(() => { // ws.send(JSON.stringify(message))