Skip to content

Commit

Permalink
test: refactor settings
Browse files Browse the repository at this point in the history
  • Loading branch information
cameri committed Oct 21, 2022
1 parent af7c48e commit 6e2da2a
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 105 deletions.
5 changes: 3 additions & 2 deletions src/adapters/web-server-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Duplex, EventEmitter } from 'stream'
import { IncomingMessage, Server, ServerResponse } from 'http'
import packageJson from '../../package.json'

import { ISettings } from '../@types/settings'
import { IWebServerAdapter } from '../@types/adapters'
import { Settings } from '../utils/settings'

export class WebServerAdapter extends EventEmitter implements IWebServerAdapter {

public constructor(
private readonly webServer: Server,
private readonly settings: () => ISettings,
) {
super()
this.webServer.on('request', this.onWebServerRequest.bind(this))
Expand All @@ -25,7 +26,7 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter
if (request.method === 'GET' && request.headers['accept'] === 'application/nostr+json') {
const {
info: { name, description, pubkey, contact },
} = Settings
} = this.settings()

const relayInformationDocument = {
name,
Expand Down
6 changes: 4 additions & 2 deletions src/adapters/web-socket-server-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IWebSocketAdapter, IWebSocketServerAdapter } from '../@types/adapters'
import { WebSocketAdapterEvent, WebSocketServerAdapterEvent } from '../constants/adapter'
import { Event } from '../@types/event'
import { Factory } from '../@types/base'
import { ISettings } from '../@types/settings'
import { propEq } from 'ramda'
import { WebServerAdapter } from './web-server-adapter'

Expand All @@ -22,9 +23,10 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock
private readonly createWebSocketAdapter: Factory<
IWebSocketAdapter,
[WebSocket, IncomingMessage, IWebSocketServerAdapter]
>
>,
settings: () => ISettings,
) {
super(webServer)
super(webServer, settings)

this.webSocketsAdapters = new WeakMap()

Expand Down
12 changes: 8 additions & 4 deletions src/factories/message-handler-factory.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IncomingMessage, MessageType } from '../@types/messages'
import { createSettings } from './settings-factory'
import { DelegatedEventMessageHandler } from '../handlers/delegated-event-message-handler'
import { delegatedEventStrategyFactory } from './delegated-event-strategy-factory'
import { EventMessageHandler } from '../handlers/event-message-handler'
import { eventStrategyFactory } from './event-strategy-factory'
import { IEventRepository } from '../@types/repositories'
import { isDelegatedEvent } from '../utils/event'
import { IWebSocketAdapter } from '../@types/adapters'
import { Settings } from '../utils/settings'
import { SubscribeMessageHandler } from '../handlers/subscribe-message-handler'
import { UnsubscribeMessageHandler } from '../handlers/unsubscribe-message-handler'

Expand All @@ -17,13 +17,17 @@ export const messageHandlerFactory = (
case MessageType.EVENT:
{
if (isDelegatedEvent(message[1])) {
return new DelegatedEventMessageHandler(adapter, delegatedEventStrategyFactory(eventRepository), Settings)
return new DelegatedEventMessageHandler(
adapter,
delegatedEventStrategyFactory(eventRepository),
createSettings
)
}

return new EventMessageHandler(adapter, eventStrategyFactory(eventRepository), Settings)
return new EventMessageHandler(adapter, eventStrategyFactory(eventRepository), createSettings)
}
case MessageType.REQ:
return new SubscribeMessageHandler(adapter, eventRepository)
return new SubscribeMessageHandler(adapter, eventRepository, createSettings)
case MessageType.CLOSE:
return new UnsubscribeMessageHandler(adapter,)
default:
Expand Down
4 changes: 4 additions & 0 deletions src/factories/settings-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ISettings } from '../@types/settings'
import { SettingsStatic } from '../utils/settings'

export const createSettings = (): ISettings => SettingsStatic.createSettings()
4 changes: 2 additions & 2 deletions src/handlers/event-message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class EventMessageHandler implements IMessageHandler {
public constructor(
protected readonly webSocket: IWebSocketAdapter,
protected readonly strategyFactory: Factory<IEventStrategy<Event, Promise<void>>, [Event, IWebSocketAdapter]>,
private readonly settings: ISettings
private readonly settings: () => ISettings
) { }

public async handleMessage(message: IncomingEventMessage): Promise<void> {
Expand Down Expand Up @@ -49,7 +49,7 @@ export class EventMessageHandler implements IMessageHandler {

protected canAcceptEvent(event: Event): string | undefined {
const now = Math.floor(Date.now()/1000)
const limits = this.settings.limits.event
const limits = this.settings().limits.event
if (limits.createdAt.maxPositiveDelta > 0) {
if (event.created_at > now + limits.createdAt.maxPositiveDelta) {
return `created_at is more than ${limits.createdAt.maxPositiveDelta} seconds in the future`
Expand Down
7 changes: 4 additions & 3 deletions src/handlers/subscribe-message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { streamEach, streamEnd, streamFilter, streamMap } from '../utils/stream'
import { SubscriptionFilter, SubscriptionId } from '../@types/subscription'
import { Event } from '../@types/event'
import { IEventRepository } from '../@types/repositories'
import { ISettings } from '../@types/settings'
import { IWebSocketAdapter } from '../@types/adapters'
import { Settings } from '../utils/settings'
import { SubscribeMessage } from '../@types/messages'
import { WebSocketAdapterEvent } from '../constants/adapter'

Expand All @@ -19,6 +19,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
public constructor(
private readonly webSocket: IWebSocketAdapter,
private readonly eventRepository: IEventRepository,
private readonly settings: () => ISettings,
) {
this.abortController = new AbortController()
}
Expand Down Expand Up @@ -66,15 +67,15 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
}

private canSubscribe(subscriptionId: string, filters: SubscriptionFilter[]): string | undefined {
const maxSubscriptions = Settings.limits.client.subscription.maxSubscriptions
const maxSubscriptions = this.settings().limits.client.subscription.maxSubscriptions
if (maxSubscriptions > 0) {
const subscriptions = this.webSocket.getSubscriptions()
if (!subscriptions.has(subscriptionId) && subscriptions.size + 1 > maxSubscriptions) {
return `Too many subscriptions: Number of subscriptions must be less than ${maxSubscriptions}`
}
}

const maxFilters = Settings.limits.client.subscription.maxFilters
const maxFilters = this.settings().limits.client.subscription.maxFilters
if (maxFilters > 0) {
if (filters.length > maxFilters) {
return `Too many filters: Number of filters per susbscription must be less or equal to ${maxFilters}`
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import http from 'http'
import process from 'process'
import { WebSocketServer } from 'ws'

import { createSettings } from './factories/settings-factory'
import { EventRepository } from './repositories/event-repository'
import { getDbClient } from './database/client'
import packageJson from '../package.json'
Expand Down Expand Up @@ -64,7 +65,8 @@ if (cluster.isPrimary) {
const adapter = new WebSocketServerAdapter(
server,
wss,
webSocketAdapterFactory(eventRepository)
webSocketAdapterFactory(eventRepository),
createSettings,
)

adapter.listen(port)
Expand Down
149 changes: 79 additions & 70 deletions src/utils/settings.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,99 @@
import { existsSync, readFileSync, writeFileSync } from 'fs'
import fs from 'fs'
import { homedir } from 'os'
import { join } from 'path'
import { mergeDeepRight } from 'ramda'

import { ISettings } from '../@types/settings'
import packageJson from '../../package.json'

export const getSettingsFilePath = (filename = 'settings.json'): string => join(
process.env.NOSTR_CONFIG_DIR ?? join(homedir(), '.nostr'),
filename,
)
export class SettingsStatic {
static _settings: ISettings

let _settings: ISettings
public static getSettingsFilePath(filename = 'settings.json') {
return join(
process.env.NOSTR_CONFIG_DIR ?? join(homedir(), '.nostr'),
filename
)
}

export const getDefaultSettings = (): ISettings => ({
info: {
relay_url: `wss://${packageJson.name}.your-domain.com`,
name: `${packageJson.name}.your-domain.com`,
description: packageJson.description,
pubkey: '',
contact: '[email protected]',
},
limits: {
event: {
eventId: {
minLeadingZeroBits: 0,
},
kind: {
whitelist: [],
blacklist: [],
},
pubkey: {
minLeadingZeroBits: 0,
whitelist: [],
blacklist: [],
},
createdAt: {
maxPositiveDelta: 900, // +15 min
maxNegativeDelta: 0, // disabled
public static getDefaultSettings(): ISettings {
return {
info: {
relay_url: 'wss://nostr-ts-relay.your-domain.com',
name: `${packageJson.name}.your-domain.com`,
description: packageJson.description,
pubkey: 'replace-with-your-pubkey',
contact: '[email protected]',
},
},
client: {
subscription: {
maxSubscriptions: 10,
maxFilters: 10,
limits: {
event: {
eventId: {
minLeadingZeroBits: 0,
},
kind: {
whitelist: [],
blacklist: [],
},
pubkey: {
minLeadingZeroBits: 0,
whitelist: [],
blacklist: [],
},
createdAt: {
maxPositiveDelta: 900,
maxNegativeDelta: 0, // disabled
},
},
client: {
subscription: {
maxSubscriptions: 10,
maxFilters: 10,
},
},
},
},
},
})

const loadSettings = (path: string) => {
return JSON.parse(
readFileSync(
path,
{ encoding: 'utf-8' },
),
)
}

const createSettings = (): ISettings => {
const path = getSettingsFilePath()
const defaults = getDefaultSettings()
try {
if (_settings) {
return _settings
}
}

public static loadSettings(path: string) {
return JSON.parse(
fs.readFileSync(
path,
{ encoding: 'utf-8' }
)
)
}

if (!existsSync(path)) {
saveSettings(path, defaults)
public static createSettings(): ISettings {
if (SettingsStatic._settings) {
return SettingsStatic._settings
}
const path = SettingsStatic.getSettingsFilePath()
const defaults = SettingsStatic.getDefaultSettings()
try {

_settings = mergeDeepRight(defaults, loadSettings(path))
if (fs.existsSync(path)) {
SettingsStatic._settings = mergeDeepRight(
defaults,
SettingsStatic.loadSettings(path)
)
} else {
SettingsStatic.saveSettings(path, defaults)
SettingsStatic._settings = mergeDeepRight({}, defaults)
}

return _settings
} catch (error) {
console.error('Unable to read config file. Reason: %s', error.message)
return SettingsStatic._settings
} catch (error) {
console.error('Unable to read config file. Reason: %s', error.message)

return defaults
return defaults
}
}
}

export const saveSettings = (path: string, settings: ISettings) => {
return writeFileSync(
path,
JSON.stringify(settings, null, 2),
{ encoding: 'utf-8' }
)
public static saveSettings(path: string, settings: ISettings) {
return fs.writeFileSync(
path,
JSON.stringify(settings, null, 2),
{ encoding: 'utf-8' }
)
}
}
export const Settings = createSettings()
24 changes: 24 additions & 0 deletions test/unit/factories/settings-factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect } from 'chai'
import Sinon from 'sinon'

import { createSettings } from '../../../src/factories/settings-factory'
import { SettingsStatic } from '../../../src/utils/settings'

describe('getSettings', () => {
let createSettingsStub: Sinon.SinonStub

beforeEach(() => {
createSettingsStub = Sinon.stub(SettingsStatic, 'createSettings')
})

afterEach(() => {
createSettingsStub.restore()
})

it('calls createSettings and returns', () => {
const settings = Symbol()
createSettingsStub.returns(settings)

expect(createSettings()).to.equal(settings)
})
})
4 changes: 2 additions & 2 deletions test/unit/handlers/event-message-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('EventMessageHandler', () => {
handler = new EventMessageHandler(
webSocket as any,
strategyFactoryStub,
{} as any,
() => ({}) as any,
)
})

Expand Down Expand Up @@ -168,7 +168,7 @@ describe('EventMessageHandler', () => {
handler = new EventMessageHandler(
{} as any,
() => null,
settings,
() => settings,
)
})

Expand Down
Loading

0 comments on commit 6e2da2a

Please sign in to comment.