Skip to content

Commit

Permalink
⚙ Seperate configs and parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
bdsoha committed Nov 5, 2024
1 parent edea5e5 commit b7f23b1
Show file tree
Hide file tree
Showing 17 changed files with 332 additions and 237 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ corresponding default settings.
If you prefer not to provide the configuration values directly, you can use environment
variables instead.

All `VBoxConfigs` configuration values can be represented as environment variables by converting
the config key to uppercase and prefixing it with `VITREA_VBOX_`.
For instance, the key `username` would be represented as `VITREA_VBOX_USERNAME`.
All `VBoxConfigs` configuration values can be represented as environment variables by
converting the config key to uppercase and prefixing it with `VITREA_VBOX_`.
For instance, the key `username` would be represented as `VITREA_VBOX_USERNAME` and
`requestTimeout` as `VITREA_VBOX_REQUEST_TIMEOUT`.

## Usage

Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vitrea-client",
"version": "0.0.3",
"version": "0.0.4",
"description": "Vitrea Smart Home API Client",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -32,14 +32,14 @@
"async-mutex": "^0.5.0"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/jest": "^29.5.12",
"eslint": "^9.11.1",
"@eslint/js": "^9.14.0",
"@types/jest": "^29.5.14",
"eslint": "^9.14.0",
"eslint-plugin-align-import": "^1.0.0",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0"
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
Expand Down
4 changes: 2 additions & 2 deletions src/VitreaClient.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Socket } from 'net'
import { VitreaClient } from './VitreaClient'
import { SocketConfigs } from './socket/AbstractSocket'
import { SocketConfigs } from './configs'
import { Login, ToggleHeartbeat } from './requests'
import { KeyStatus, RoomMetaData } from './responses'
import { BaseRequest, BaseResponse } from './core'
Expand All @@ -9,7 +9,7 @@ import * as Exceptions from './exceptions'
describe('VitreaClient', () => {
jest.useFakeTimers()

const getClient = (configs?: SocketConfigs) => {
const getClient = (configs?: Partial<SocketConfigs>) => {
return VitreaClient.create({
username: 'admin',
password: 'secret'
Expand Down
39 changes: 22 additions & 17 deletions src/VitreaClient.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { Mutex } from 'async-mutex'
import { Events } from './utilities/Events'
import { Timeout } from './socket/Timeout'
import { KeyStatus } from './responses'
import { ProtocolVersion } from './utilities/ProtocolVersion'
import { ResponseFactory } from './responses/helpers'
import { SplitMultipleBuffers } from './utilities/SplitMultipleBuffers'
import { Login, ToggleHeartbeat } from './requests'
import { VitreaHeartbeatHandler } from './socket/VitreaHeartbeatHandler'
import { VBoxConfigs, VBoxConnection } from './utilities/VBoxConnection'
import { AbstractSocket, SocketConfigs } from './socket/AbstractSocket'
import * as Core from './core'
import { Mutex } from 'async-mutex'
import { Events } from './utilities/Events'
import { Timeout } from './socket/Timeout'
import { KeyStatus } from './responses'
import { AbstractSocket } from './socket/AbstractSocket'
import { ResponseFactory } from './responses/helpers'
import { SplitMultipleBuffers } from './utilities/SplitMultipleBuffers'
import { Login, ToggleHeartbeat } from './requests'
import { VitreaHeartbeatHandler } from './socket/VitreaHeartbeatHandler'
import * as Core from './core'
import {
ConnectionConfigs,
ConnectionConfigParser,
ProtocolVersion,
SocketConfigs,
SocketConfigParser
} from './configs'


export class VitreaClient extends AbstractSocket {
protected readonly mutex = new Mutex()
protected readonly configs: VBoxConfigs
protected readonly configs: ConnectionConfigs
protected readonly version: ProtocolVersion

protected constructor(configs: Required<VBoxConfigs>, socketConfigs: SocketConfigs) {
protected constructor(configs: ConnectionConfigs, socketConfigs: SocketConfigs) {
super(configs.host, configs.port, socketConfigs)
this.configs = configs
this.heartbeat = new VitreaHeartbeatHandler(this)
Expand Down Expand Up @@ -107,10 +112,10 @@ export class VitreaClient extends AbstractSocket {
this.on(Events.STATUS_UPDATE, listener)
}

public static create(configs: Partial<VBoxConfigs> = {}, socketConfigs: SocketConfigs = {}) {
public static create(configs: Partial<ConnectionConfigs> = {}, socketConfigs: Partial<SocketConfigs> = {}) {
return new this(
VBoxConnection.create(configs),
socketConfigs
ConnectionConfigParser.create(configs),
SocketConfigParser.create(socketConfigs)
)
}
}
23 changes: 23 additions & 0 deletions src/configs/AbstractConfigParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export abstract class AbstractConfigParser<R> {
protected constructor(protected readonly configs: Partial<R>) { }

protected validate<T extends keyof R>(lookup: string, found: string | R[T]) {
if (!found && found !== false) {
throw TypeError(`A value for [${lookup}] is required`)
}

return found
}

protected get<T extends keyof R>(
key: T,
fallback: R[T] = undefined,
) {
const lookup: string = key as string
const envLookup = lookup.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase()

const found = this.configs[key] ?? process.env[`VITREA_VBOX_${envLookup}`] ?? fallback

return this.validate(lookup, found)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { VBoxConnection } from './VBoxConnection'
import { ProtocolVersion } from './ProtocolVersion'
import { ConnectionConfigParser, ProtocolVersion } from './ConnectionConfigParser'


describe('VBoxConnection', () => {
describe('ConnectionConfigParser', () => {
const env = process.env

beforeEach(() => {
Expand All @@ -19,7 +18,7 @@ describe('VBoxConnection', () => {
process.env.VITREA_VBOX_PASSWORD = 'secret'
process.env.VITREA_VBOX_VERSION = 'v1'

const client = VBoxConnection.create()
const client = ConnectionConfigParser.create()

expect(client).toStrictEqual({
host: '192.168.1.111',
Expand All @@ -31,7 +30,7 @@ describe('VBoxConnection', () => {
})

it('[create] uses parameters when available', () => {
const client = VBoxConnection.create({
const client = ConnectionConfigParser.create({
host: '192.168.1.111',
port: 1234,
username: 'admin',
Expand All @@ -49,7 +48,7 @@ describe('VBoxConnection', () => {
})

it('[create] uses a default port and host', () => {
const client = VBoxConnection.create({
const client = ConnectionConfigParser.create({
username: 'admin',
password: 'secret'
})
Expand All @@ -64,23 +63,23 @@ describe('VBoxConnection', () => {
})

it('[create] raises an exception for missing host', () => {
const callback = () => VBoxConnection.create({
host: '',
username: 'admin',
password: 'secret'
const callback = () => ConnectionConfigParser.create({
host: '',
username: 'admin',
password: 'secret'
})

expect(callback).toThrow(TypeError)
})

it('[create] raises an exception for missing username', () => {
const callback = () => VBoxConnection.create({ password: 'secret' })
const callback = () => ConnectionConfigParser.create({ password: 'secret' })

expect(callback).toThrow(TypeError)
})

it('[create] raises an exception for missing password', () => {
const callback = () => VBoxConnection.create({ username: 'admin' })
const callback = () => ConnectionConfigParser.create({ username: 'admin' })

expect(callback).toThrow(TypeError)
})
Expand Down
30 changes: 30 additions & 0 deletions src/configs/ConnectionConfigParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { AbstractConfigParser } from './AbstractConfigParser'

export interface ConnectionConfigs {
host: string
port: number
username: string
password: string
version: ProtocolVersion
}

export const ProtocolVersion = {
V1: 'v1',
V2: 'v2'
} as const

export type ProtocolVersion = typeof ProtocolVersion[keyof typeof ProtocolVersion]

export class ConnectionConfigParser extends AbstractConfigParser<ConnectionConfigs> {
public static create(configs: Partial<ConnectionConfigs> = {}): ConnectionConfigs {
const instance = new this(configs)

return {
host: instance.get('host', '192.168.1.23'),
port: Number(instance.get('port', 11501)),
username: instance.get('username'),
password: instance.get('password'),
version: instance.get('version', ProtocolVersion.V2) as ProtocolVersion,
}
}
}
49 changes: 49 additions & 0 deletions src/configs/SocketConfigParser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { SocketConfigParser } from './SocketConfigParser'
import { ConsoleLogger, NullLogger } from '../core'


describe('SocketConfigParser', () => {
const env = process.env

beforeEach(() => {
jest.resetModules()
process.env = { ...env }
})

afterEach(() => process.env = env)

it('[create] has default values', () => {
const configs = SocketConfigParser.create()

expect(configs.log).toBeInstanceOf(NullLogger)
expect(configs.socketSupplier).toBeInstanceOf(Function)
expect(configs.shouldReconnect).toBeTruthy()
expect(configs.requestTimeout).toBe(1000)
})

it('[create] uses environment variables when available', () => {
process.env.VITREA_VBOX_REQUEST_TIMEOUT = '2000'
process.env.VITREA_VBOX_SHOULD_RECONNECT = 'false'

const configs = SocketConfigParser.create()

expect(configs.shouldReconnect).toBeFalsy()
expect(configs.requestTimeout).toBe(2000)
})

it('[create] uses parameters when available', () => {
const supplier = jest.fn()

const configs = SocketConfigParser.create({
socketSupplier: supplier,
shouldReconnect: false,
requestTimeout: 2000,
log: new ConsoleLogger()
})

expect(configs.log).toBeInstanceOf(ConsoleLogger)
expect(configs.socketSupplier).toBe(supplier)
expect(configs.shouldReconnect).toBeFalsy()
expect(configs.requestTimeout).toBe(2000)
})
})
28 changes: 28 additions & 0 deletions src/configs/SocketConfigParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AbstractConfigParser } from './AbstractConfigParser'
import { LoggerContract, NullLogger } from '../core'
import * as Net from 'net'

export interface SocketConfigs {
log: LoggerContract,
shouldReconnect: boolean
requestTimeout: number

socketSupplier(): Net.Socket
}

export class SocketConfigParser extends AbstractConfigParser<SocketConfigs> {
public toBoolean(value: string | boolean): boolean {
return ['1', 'true', 'TRUE', true].includes(value)
}

public static create(configs: Partial<SocketConfigs> = {}): Required<SocketConfigs> {
const instance = new this(configs)

return {
log: instance.configs.log ?? new NullLogger(),
socketSupplier: instance.configs.socketSupplier ?? (() => new Net.Socket()),
shouldReconnect: instance.toBoolean(instance.get('shouldReconnect', true)),
requestTimeout: Number(instance.get('requestTimeout', 1000))
}
}
}
6 changes: 6 additions & 0 deletions src/configs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { SocketConfigs, SocketConfigParser } from './SocketConfigParser'
export {
ConnectionConfigs,
ConnectionConfigParser,
ProtocolVersion
} from './ConnectionConfigParser'
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { ConsoleLogger } from './core'
export { VitreaClient } from './VitreaClient'
export { ProtocolVersion } from './utilities/ProtocolVersion'
export { ProtocolVersion } from './configs'
export * as Enums from './utilities/Enums'
export * as Exceptions from './exceptions'
export * as Requests from './requests'
Expand Down
2 changes: 1 addition & 1 deletion src/responses/helpers/ResponseFactory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RoomCount } from '../RoomCount'
import { NodeMetaDataV2 } from '../NodeMetaDataV2'
import { ProtocolVersion } from '../../utilities/ProtocolVersion'
import { ProtocolVersion } from '../../configs'
import { ResponseFactory } from './ResponseFactory'
import { DataGramDirection } from '../../utilities/Enums'

Expand Down
2 changes: 1 addition & 1 deletion src/responses/helpers/ResponseFactory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommandID } from '../ResponseCodes'
import { ProtocolVersion } from '../../utilities/ProtocolVersion'
import { ProtocolVersion } from '../../configs'
import { DataGramDirection } from '../../utilities/Enums'
import { BaseResponse, DataGram } from '../../core'
import * as Responses from '..'
Expand Down
Loading

0 comments on commit b7f23b1

Please sign in to comment.