Skip to content

Commit

Permalink
Add optional logging; update example app (#44)
Browse files Browse the repository at this point in the history
* Add optional logging; update example app

* linter fixes
  • Loading branch information
dmoss18 authored Sep 17, 2024
1 parent 9548e7d commit 23ac561
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 25 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [5.0.0] - 2024-09-17

The `TastytradeClient` constructor now takes a single config object instead of 2 urls.

### Changed

- Add optional logging; update example app [#44](https://github.com/tastytrade/tastytrade-api-js/pull/44)
- Add future option quote streaming example to README

### Fixed

- Update README.md [#42](https://github.com/tastytrade/tastytrade-api-js/pull/35)
- README corrections
- typo on json account node name [#39](https://github.com/tastytrade/tastytrade-api-js/pull/39)

## [4.0.0] - 2024-03-12

### Changed
Expand Down
1 change: 1 addition & 0 deletions examples/components/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const Layout = observer((props: any) => {
<Toaster
toastOptions={{
duration: 5000,
position: 'bottom-right',
success: {
duration: 10000
}
Expand Down
10 changes: 6 additions & 4 deletions examples/contexts/context.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// src/context/state.ts
import { createContext } from 'react';
import TastytradeClient, { MarketDataStreamer } from "tastytrade-api"
import TastytradeClient, { LogLevel, MarketDataStreamer } from "tastytrade-api"
import { makeAutoObservable } from 'mobx';
import _ from 'lodash'

const SANDBOX_BASE_URL = 'https://api.cert.tastyworks.com'
const SANDBOX_STREAMER_URL = 'wss://streamer.cert.tastyworks.com'

class TastytradeContext {
static Instance = new TastytradeContext('https://api.cert.tastyworks.com', 'wss://streamer.cert.tastyworks.com');
public tastytradeApi: TastytradeClient
public accountNumbers: string[] = []
public readonly marketDataStreamer: MarketDataStreamer = new MarketDataStreamer()

constructor(baseUrl: string, accountStreamerUrl: string) {
makeAutoObservable(this)
this.tastytradeApi = new TastytradeClient(baseUrl, accountStreamerUrl)
this.tastytradeApi = new TastytradeClient({ baseUrl, accountStreamerUrl, logger: console, logLevel: LogLevel.INFO })
makeAutoObservable(this.tastytradeApi.session)
}

Expand All @@ -21,6 +23,6 @@ class TastytradeContext {
}
}

const AppContext = createContext(TastytradeContext.Instance)
const AppContext = createContext<TastytradeContext>(new TastytradeContext(SANDBOX_BASE_URL, SANDBOX_STREAMER_URL))

export { AppContext, TastytradeContext };
16 changes: 9 additions & 7 deletions examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useMemo } from 'react'
function MyApp({ Component, pageProps }: AppProps) {

const context = useMemo(
() => new TastytradeContext('https://api.tastyworks.com', 'wss://streamer.cert.tastyworks.com'),
() => new TastytradeContext('https://api.cert.tastyworks.com', 'wss://streamer.cert.tastyworks.com'),
[]
);
return (
Expand Down
23 changes: 15 additions & 8 deletions lib/account-streamer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { JsonMap, JsonValue } from './utils/json-util.js'
import { JsonBuilder } from './utils/json-util.js'
import TastytradeSession from './models/tastytrade-session.js'
import { MinTlsVersion } from './utils/constants.js'
import type Logger from './logger.js'

export enum STREAMER_STATE {
Open = 0,
Expand Down Expand Up @@ -42,6 +43,7 @@ function removeElement<T>(array: T[], element: T): void {
}

export class AccountStreamer {
private readonly logger: Logger
private websocket: WebSocket | null = null
private startResolve: ((result: boolean) => void) | null = null
private startReject: ((reason?: any) => void) | null = null

Check warning on line 49 in lib/account-streamer.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 49 in lib/account-streamer.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
Expand All @@ -65,13 +67,17 @@ export class AccountStreamer {
[(status: string) => void, (error: string) => void]
> = new Map()

private readonly logger = console

/**
*
* @param url Url of the account streamer service
*/
constructor(private readonly url: string, private readonly session: TastytradeSession) {}
constructor(
private readonly url: string,
private readonly session: TastytradeSession,
logger: Logger
) {
this.logger = logger
}

get streamerState(): STREAMER_STATE {
return this._streamerState
Expand Down Expand Up @@ -237,6 +243,7 @@ export class AccountStreamer {
// Queue up and send on open
this.queued.push(message)
} else {
this.logger.info('Sending message: ', message)
websocket.send(message)
}

Expand Down Expand Up @@ -304,12 +311,12 @@ export class AccountStreamer {
this.queued = []
}

private readonly handleOpen = (event: WebSocket.Event) => {
private readonly handleOpen = (_event: WebSocket.Event) => {
if (this.startResolve === null) {
return
}

this.logger.info('AccountStreamer opened', event)
this.logger.info('AccountStreamer opened')

this.startResolve(true)
this.startResolve = this.startReject = null
Expand All @@ -320,7 +327,7 @@ export class AccountStreamer {
}

private readonly handleClose = (event: WebSocket.CloseEvent) => {
this.logger.info('AccountStreamer closed', event)
this.logger.info('AccountStreamer closed')
if (this.websocket === null) {
return
}
Expand All @@ -335,7 +342,7 @@ export class AccountStreamer {
return
}

this.logger.warn('AccountStreamer error', event)
this.logger.error('AccountStreamer error', event)

this.lastErrorEvent = event
this.streamerState = STREAMER_STATE.Error
Expand Down Expand Up @@ -370,7 +377,7 @@ export class AccountStreamer {
}

private readonly handleOneMessage = (json: JsonMap) => {
this.logger.info(json)
this.logger.info('Message received: ', json)

const action = json.action as string
this.streamerMessageObservers.forEach(observer => observer(json))
Expand Down
48 changes: 48 additions & 0 deletions lib/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import _ from 'lodash'

export default interface Logger {
error(...data: any[]): void;

Check warning on line 4 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 4 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
info(...data: any[]): void;

Check warning on line 5 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 5 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
warn(...data: any[]): void;

Check warning on line 6 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 6 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
}

export enum LogLevel {
INFO = 1,
WARN = 2,
ERROR = 3
}

export class TastytradeLogger implements Logger {
public logLevel: LogLevel
private logger: Logger | null = null

constructor(logger?: Logger, logLevel?: LogLevel) {
this.logger = logger ?? null
this.logLevel = logLevel ?? LogLevel.ERROR
}

error(...data: any[]): void {

Check warning on line 24 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 24 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
if (this.shouldLog(LogLevel.ERROR)) {
this.logger!.error(...data)
}
}

info(...data: any[]): void {

Check warning on line 30 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 30 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
if (this.shouldLog(LogLevel.INFO)) {
this.logger!.info(...data)
}
}

warn(...data: any[]): void {

Check warning on line 36 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type

Check warning on line 36 in lib/logger.ts

View workflow job for this annotation

GitHub Actions / build (21.x)

Unexpected any. Specify a different type
if (this.shouldLog(LogLevel.WARN)) {
this.logger!.warn(...data)
}
}

private shouldLog(level: LogLevel) {
if (_.isNil(this.logger)) {
return false
}
return LogLevel[level] >= LogLevel[this.logLevel]
}
}
8 changes: 6 additions & 2 deletions lib/services/tastytrade-http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import axios from "axios"
import qs from 'qs'
import { recursiveDasherizeKeys } from "../utils/json-util.js"
import _ from 'lodash'
import type Logger from "../logger.js"

const ParamsSerializer = {
serialize: function (queryParams: object) {
return qs.stringify(queryParams, { arrayFormat: 'brackets' })
}
}

export default class TastytradeHttpClient{
export default class TastytradeHttpClient {
private readonly logger?: Logger
public readonly session: TastytradeSession

constructor(private readonly baseUrl: string) {
constructor(private readonly baseUrl: string, logger?: Logger) {
this.logger = logger
this.session = new TastytradeSession()
}

Expand Down Expand Up @@ -47,6 +50,7 @@ export default class TastytradeHttpClient{
paramsSerializer: ParamsSerializer
}, _.isEmpty)

this.logger?.info('Making request', config)
return axios.request(config)
}

Expand Down
19 changes: 16 additions & 3 deletions lib/tastytrade-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,18 @@ import SymbolSearchService from "./services/symbol-search-service.js"
import TransactionsService from "./services/transactions-service.js"
import WatchlistsService from "./services/watchlists-service.js"
import TastytradeSession from "./models/tastytrade-session.js"
import type Logger from "./logger.js"
import { TastytradeLogger, LogLevel } from "./logger.js"

export type ClientConfig = {
baseUrl: string,
accountStreamerUrl: string,
logger?: Logger,
logLevel?: LogLevel
}

export default class TastytradeClient {
public readonly logger: TastytradeLogger
public readonly httpClient: TastytradeHttpClient

public readonly accountStreamer: AccountStreamer
Expand All @@ -37,9 +47,10 @@ export default class TastytradeClient {
public readonly transactionsService: TransactionsService
public readonly watchlistsService: WatchlistsService

constructor(readonly baseUrl: string, readonly accountStreamerUrl: string) {
this.httpClient = new TastytradeHttpClient(baseUrl)
this.accountStreamer = new AccountStreamer(accountStreamerUrl, this.session)
constructor(config: ClientConfig) {
this.logger = new TastytradeLogger(config.logger, config.logLevel)
this.httpClient = new TastytradeHttpClient(config.baseUrl, this.logger)
this.accountStreamer = new AccountStreamer(config.accountStreamerUrl, this.session, this.logger)

this.sessionService = new SessionService(this.httpClient)
this.accountStatusService = new AccountStatusService(this.httpClient)
Expand All @@ -63,3 +74,5 @@ export default class TastytradeClient {

export { MarketDataStreamer, MarketDataSubscriptionType, type MarketDataListener, type CandleSubscriptionOptions, CandleType }
export { AccountStreamer, STREAMER_STATE, type Disposer, type StreamerStateObserver }
export { TastytradeLogger, LogLevel }
export type { Logger }

0 comments on commit 23ac561

Please sign in to comment.