diff --git a/README.md b/README.md
index e804678..d206eeb 100644
--- a/README.md
+++ b/README.md
@@ -18,30 +18,36 @@ const accountPositions = await tastytradeClient.balancesAndPositionsService.getP
### Market Data
```js
-import TastytradeClient, { QuoteStreamer } from "@tastytrade-api"
+import TastytradeClient, { MarketDataStreamer, MarketDataSubscriptionType } from "@tastytrade-api"
const tastytradeClient = new TastytradeClient(baseUrl, accountStreamerUrl)
await tastytradeClient.sessionService.login(usernameOrEmail, pasword)
-const tokenResponse = await tastytradeClient.AccountsAndCustomersService.getQuoteStreamerTokens()
-const quoteStreamer = new QuoteStreamer(tokenResponse.token, `${tokenResponse['websocket-url']}/cometd`)
-quoteStreamer.connect()
+const tokenResponse = await tastytradeClient.AccountsAndCustomersService.getApiQuoteToken()
+const streamer = new MarketDataStreamer()
+streamer.connect(tokenResponse['dxlink-url'], tokenResponse.token)
-function handleMarketDataReceived(event) {
+function handleMarketDataReceived(data) {
// Triggers every time market data event occurs
- console.log(event)
+ console.log(data)
}
+
+// Add a listener for incoming market data. Returns a remove() function that removes the listener from the quote streamer
+const removeDataListener = streamer.addDataListener(handleMarketDataReceived)
+
// Subscribe to a single equity quote
-quoteStreamer.subscribe('AAPL', handleMarketDataReceived)
+streamer.addSubscription('AAPL')
+// Optionally specify which market data events you want to subscribe to
+streamer.addSubscription('SPY', { subscriptionTypes: [MarketDataSubscriptionType.Quote] })
// Subscribe to a single equity option quote
const optionChain = await tastytradeClient.instrumentsService.getOptionChain('AAPL')
-quoteStreamer.subscribe(optionChain[0]['streamer-symbol'], handleMarketDataReceived)
+streamer.addSubscription(optionChain[0]['streamer-symbol'])
```
### Account Streamer
```js
const TastytradeApi = require("@tastytrade/api")
const TastytradeClient = TastytradeApi.default
-const { AccountStreamer, QuoteStreamer } = TastytradeApi
+const { AccountStreamer } = TastytradeApi
const _ = require('lodash')
function handleStreamerMessage(json) {
diff --git a/examples/components/custom-table.tsx b/examples/components/custom-table.tsx
index a90b22b..2d48eb9 100644
--- a/examples/components/custom-table.tsx
+++ b/examples/components/custom-table.tsx
@@ -7,10 +7,16 @@ interface Props{
}
export default function CustomTable(props:Props) {
- return (
-
- {props.rows.map(props.renderItem)}
+ const renderRow = (item: any, index: number) => {
+
+ {props.renderItem(item, index)}
+ }
+
+ return (
+ <>
+ {props.rows.map(renderRow)}
+ >
)
}
diff --git a/examples/components/subscribed-symbol.tsx b/examples/components/subscribed-symbol.tsx
index 05bebd7..719d7f2 100644
--- a/examples/components/subscribed-symbol.tsx
+++ b/examples/components/subscribed-symbol.tsx
@@ -1,8 +1,7 @@
import { useContext, useEffect, useState } from 'react'
import _ from 'lodash'
import { AppContext } from '../contexts/context'
-import Button from './button'
-import { EventType } from '@dxfeed/api'
+import { MarketDataSubscriptionType } from "tastytrade-api"
export default function SubscribedSymbol(props: any) {
const [bidPrice, setBidPrice] = useState(NaN)
@@ -10,17 +9,24 @@ export default function SubscribedSymbol(props: any) {
const appContext = useContext(AppContext)
- const handleEvent = (event: any) => {
- console.log(event)
- if (event.eventType === EventType.Quote) {
- setBidPrice(event.bidPrice)
- setAskPrice(event.askPrice)
- }
- }
-
useEffect(() => {
- const unsubscribe = appContext.quoteStreamer!.subscribe(props.symbol, handleEvent)
- return unsubscribe
+ const removeListener = appContext.marketDataStreamer.addDataListener((event: any) => {
+ const eventData = _.find(event.data, data =>
+ data.eventType === MarketDataSubscriptionType.Quote &&
+ data.eventSymbol === props.symbol
+ )
+
+ if (!_.isNil(eventData)) {
+ setBidPrice(eventData.bidPrice)
+ setAskPrice(eventData.askPrice)
+ }
+ })
+
+ appContext.marketDataStreamer.addSubscription(props.symbol)
+ return () => {
+ appContext.marketDataStreamer.removeSubscription(props.symbol)
+ removeListener()
+ }
}, []);
return (
diff --git a/examples/contexts/context.ts b/examples/contexts/context.ts
index c45dc80..3aa581d 100644
--- a/examples/contexts/context.ts
+++ b/examples/contexts/context.ts
@@ -1,14 +1,14 @@
// src/context/state.ts
import { createContext } from 'react';
-import TastytradeClient, { QuoteStreamer } from "tastytrade-api"
+import TastytradeClient, { MarketDataStreamer } from "tastytrade-api"
import { makeAutoObservable } from 'mobx';
import _ from 'lodash'
class TastytradeContext {
- static Instance = new TastytradeContext('https://api.tastyworks.com', 'wss://streamer.tastyworks.com');
+ static Instance = new TastytradeContext('https://api.cert.tastyworks.com', 'wss://streamer.cert.tastyworks.com');
public tastytradeApi: TastytradeClient
public accountNumbers: string[] = []
- public quoteStreamer: QuoteStreamer | null = null
+ public readonly marketDataStreamer: MarketDataStreamer = new MarketDataStreamer()
constructor(baseUrl: string, accountStreamerUrl: string) {
makeAutoObservable(this)
@@ -16,14 +16,6 @@ class TastytradeContext {
makeAutoObservable(this.tastytradeApi.session)
}
- setupQuoteStreamer(token: string, url: string) {
- if (_.isNil(this.quoteStreamer)) {
- this.quoteStreamer = new QuoteStreamer(token, url)
- }
-
- return this.quoteStreamer
- }
-
get isLoggedIn() {
return this.tastytradeApi.session.isValid
}
diff --git a/examples/next.config.js b/examples/next.config.js
index a843cbe..53e2dc0 100644
--- a/examples/next.config.js
+++ b/examples/next.config.js
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- reactStrictMode: true,
+ reactStrictMode: false
}
module.exports = nextConfig
diff --git a/examples/package-lock.json b/examples/package-lock.json
index 222b75a..fe8deca 100644
--- a/examples/package-lock.json
+++ b/examples/package-lock.json
@@ -37,19 +37,22 @@
},
"..": {
"name": "@tastytrade/api",
- "version": "0.0.1",
+ "version": "1.0.0",
"license": "MIT",
"dependencies": {
- "@dxfeed/api": "^1.1.0",
"@types/lodash": "^4.14.182",
"@types/qs": "^6.9.7",
"axios": "^1.3.4",
+ "isomorphic-ws": "^5.0.0",
"lodash": "^4.17.21",
- "qs": "^6.11.1"
+ "qs": "^6.11.1",
+ "uuid": "^9.0.0",
+ "ws": "^8.13.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "17.0.27",
+ "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"dotenv": "^16.0.3",
@@ -11433,21 +11436,24 @@
"tastytrade-api": {
"version": "file:..",
"requires": {
- "@dxfeed/api": "^1.1.0",
"@types/jest": "^29.5.0",
"@types/lodash": "^4.14.182",
"@types/node": "17.0.27",
"@types/qs": "^6.9.7",
+ "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"axios": "^1.3.4",
"dotenv": "^16.0.3",
"eslint": "^8.14.0",
+ "isomorphic-ws": "^5.0.0",
"jest": "^29.5.0",
"lodash": "^4.17.21",
"qs": "^6.11.1",
"ts-jest": "^29.0.5",
- "typescript": "4.6.3"
+ "typescript": "4.6.3",
+ "uuid": "^9.0.0",
+ "ws": "^8.13.0"
}
},
"test-exclude": {
diff --git a/examples/pages/_app.tsx b/examples/pages/_app.tsx
index a31d9e9..a196040 100644
--- a/examples/pages/_app.tsx
+++ b/examples/pages/_app.tsx
@@ -7,7 +7,7 @@ import { useMemo } from 'react'
function MyApp({ Component, pageProps }: AppProps) {
const context = useMemo(
- () => new TastytradeContext('https://api.tastyworks.com', 'wss://streamer.tastyworks.com'),
+ () => new TastytradeContext('https://api.tastyworks.com', 'wss://streamer.cert.tastyworks.com'),
[]
);
return (
diff --git a/examples/pages/quote-streamer.tsx b/examples/pages/quote-streamer.tsx
index 9d6b876..0c56ce5 100644
--- a/examples/pages/quote-streamer.tsx
+++ b/examples/pages/quote-streamer.tsx
@@ -13,18 +13,17 @@ const Home: NextPage = () => {
const appContext = useContext(AppContext)
useEffect(() => {
- appContext.tastytradeApi.accountsAndCustomersService.getQuoteStreamerTokens()
+ appContext.tastytradeApi.accountsAndCustomersService.getApiQuoteToken()
.then(response => {
- const url = `${response['websocket-url']}/cometd`
- const quoteStreamer = appContext.setupQuoteStreamer(response.token, url)
- quoteStreamer.connect()
+ if (appContext.marketDataStreamer.isConnected) {
+ return
+ }
+ appContext.marketDataStreamer.connect(response['dxlink-url'], response.token)
setLoading(false)
})
return () => {
- if (!_.isNil(appContext.quoteStreamer)) {
- appContext.quoteStreamer.disconnect()
- }
+ appContext.marketDataStreamer.disconnect()
}
}, []);
@@ -48,7 +47,7 @@ const Home: NextPage = () => {
return (
- DxFeed Quotes Demo
+ DxLink Quotes Demo
Type a symbol into the input and click 'Add Symbol'
diff --git a/lib/market-data-streamer.ts b/lib/market-data-streamer.ts
new file mode 100644
index 0000000..404cabf
--- /dev/null
+++ b/lib/market-data-streamer.ts
@@ -0,0 +1,273 @@
+import WebSocket from 'isomorphic-ws'
+import _ from 'lodash'
+import { v4 as uuidv4 } from 'uuid'
+
+export enum MarketDataSubscriptionType {
+ Quote = 'Quote',
+ Trade = 'Trade',
+ Summary = 'Summary',
+ Profile = 'Profile',
+ Greeks = 'Greeks',
+ Underlying = 'Underlying'
+}
+
+export type MarketDataListener = (data: any) => void
+
+type QueuedSubscription = { symbol: string, subscriptionTypes: MarketDataSubscriptionType[] }
+type SubscriptionOptions = { subscriptionTypes: MarketDataSubscriptionType[], channelId: number }
+
+const AllSubscriptionTypes = Object.values(MarketDataSubscriptionType)
+
+const KeepaliveInterval = 30000 // 30 seconds
+
+const DefaultChannelId = 1
+
+export default class MarketDataStreamer {
+ private webSocket: WebSocket | null = null
+ private token = ''
+ private keepaliveIntervalId: any | null = null
+ private dataListeners = new Map()
+ private openChannels = new Set()
+ private subscriptionsQueue: Map
= new Map()
+ private authState = ''
+
+ addDataListener(dataListener: MarketDataListener) {
+ if (_.isNil(dataListener)) {
+ return _.noop
+ }
+ const guid = uuidv4()
+ this.dataListeners.set(guid, dataListener)
+
+ return () => this.dataListeners.delete(guid)
+ }
+
+ connect(url: string, token: string) {
+ if (this.isConnected) {
+ throw new Error('MarketDataStreamer is attempting to connect when an existing websocket is already connected')
+ }
+
+ this.token = token
+ this.webSocket = new WebSocket(url)
+ this.webSocket.onopen = this.onOpen.bind(this)
+ this.webSocket.onerror = this.onError.bind(this)
+ this.webSocket.onmessage = this.handleMessageReceived.bind(this)
+ this.webSocket.onclose = this.onClose.bind(this)
+ }
+
+ disconnect() {
+ if (_.isNil(this.webSocket)) {
+ return
+ }
+
+ this.clearKeepalive()
+
+ this.webSocket.close()
+ this.webSocket = null
+
+ this.openChannels.clear()
+ this.subscriptionsQueue.clear()
+ this.authState = ''
+ }
+
+ // TODO: add listener to options, return unsubscriber
+ addSubscription(symbol: string, options = { subscriptionTypes: AllSubscriptionTypes, channelId: DefaultChannelId }) {
+ const { subscriptionTypes, channelId } = options
+ const isOpen = this.isChannelOpened(channelId)
+ if (isOpen) {
+ this.sendSubscriptionMessage(symbol, subscriptionTypes, channelId, 'add')
+ } else {
+ this.queueSubscription(symbol, options)
+ }
+ }
+
+ removeSubscription(symbol: string, options = { subscriptionTypes: AllSubscriptionTypes, channelId: DefaultChannelId }) {
+ const { subscriptionTypes, channelId } = options
+ const isOpen = this.isChannelOpened(channelId)
+ if (isOpen) {
+ this.sendSubscriptionMessage(symbol, subscriptionTypes, channelId, 'remove')
+ } else {
+ this.dequeueSubscription(symbol, options)
+ }
+ }
+
+ removeAllSubscriptions(channelId = DefaultChannelId) {
+ const isOpen = this.isChannelOpened(channelId)
+ if (isOpen) {
+ this.sendMessage({ "type": "FEED_SUBSCRIPTION", "channel": channelId, reset: true })
+ } else {
+ this.subscriptionsQueue.set(channelId, [])
+ }
+ }
+
+ openFeedChannel(channelId: number) {
+ if (!this.isDxLinkAuthorized) {
+ throw new Error(`Unable to open channel ${channelId} due to DxLink authorization state: ${this.authState}`)
+ }
+
+ if (this.isChannelOpened(channelId)) {
+ return
+ }
+
+ this.sendMessage({
+ "type": "CHANNEL_REQUEST",
+ "channel": channelId,
+ "service": "FEED",
+ "parameters": {
+ "contract": "AUTO"
+ }
+ })
+ }
+
+ isChannelOpened(channelId: number) {
+ return this.isConnected && this.openChannels.has(channelId)
+ }
+
+ get isConnected() {
+ return !_.isNil(this.webSocket)
+ }
+
+ private scheduleKeepalive() {
+ this.keepaliveIntervalId = setInterval(this.sendKeepalive, KeepaliveInterval)
+ }
+
+ private sendKeepalive() {
+ if (_.isNil(this.keepaliveIntervalId)) {
+ return
+ }
+
+ this.sendMessage({
+ "type": "KEEPALIVE",
+ "channel": 0
+ })
+ }
+
+ private queueSubscription(symbol: string, options: SubscriptionOptions) {
+ const { subscriptionTypes, channelId } = options
+ let queue = this.subscriptionsQueue.get(options.channelId)
+ if (_.isNil(queue)) {
+ queue = []
+ this.subscriptionsQueue.set(channelId, queue)
+ }
+
+ queue.push({ symbol, subscriptionTypes })
+ }
+
+ private dequeueSubscription(symbol: string, options: SubscriptionOptions) {
+ const queue = this.subscriptionsQueue.get(options.channelId)
+ if (_.isNil(queue) || _.isEmpty(queue)) {
+ return
+ }
+
+ _.remove(queue, (queueItem: any) => queueItem.symbol === symbol)
+ }
+
+ private sendQueuedSubscriptions(channelId: number) {
+ const queuedSubscriptions = this.subscriptionsQueue.get(channelId)
+ if (_.isNil(queuedSubscriptions)) {
+ return
+ }
+
+ // Clear out queue immediately
+ this.subscriptionsQueue.set(channelId, [])
+ queuedSubscriptions.forEach(subscription => {
+ this.sendSubscriptionMessage(subscription.symbol, subscription.subscriptionTypes, channelId, 'add')
+ })
+ }
+
+ /**
+ *
+ * @param {*} symbol
+ * @param {*} subscriptionTypes
+ * @param {*} channelId
+ * @param {*} direction add or remove
+ */
+ private sendSubscriptionMessage(symbol: string, subscriptionTypes: MarketDataSubscriptionType[], channelId: number, direction: string) {
+ const subscriptions = subscriptionTypes.map(type => ({ "symbol": symbol, "type": type }))
+ this.sendMessage({
+ "type": "FEED_SUBSCRIPTION",
+ "channel": channelId,
+ [direction]: subscriptions
+ })
+ }
+
+ private onError(error: any) {
+ console.error('Error received: ', error)
+ }
+
+ private onOpen() {
+ this.openChannels.clear()
+
+ this.sendMessage({
+ "type": "SETUP",
+ "channel": 0,
+ "keepaliveTimeout": KeepaliveInterval,
+ "acceptKeepaliveTimeout": KeepaliveInterval,
+ "version": "0.1-js/1.0.0"
+ })
+
+ this.scheduleKeepalive()
+ }
+
+ private onClose() {
+ this.webSocket = null
+ this.clearKeepalive()
+ }
+
+ private clearKeepalive() {
+ if (!_.isNil(this.keepaliveIntervalId)) {
+ clearInterval(this.keepaliveIntervalId)
+ }
+
+ this.keepaliveIntervalId = null
+ }
+
+ get isDxLinkAuthorized() {
+ return this.authState === 'AUTHORIZED'
+ }
+
+ private handleAuthStateMessage(data: any) {
+ this.authState = data.state
+ if (this.isDxLinkAuthorized) {
+ this.openFeedChannel(DefaultChannelId)
+ } else {
+ this.sendMessage({
+ "type": "AUTH",
+ "channel": 0,
+ "token": this.token
+ })
+ }
+ }
+
+ private handleChannelOpened(jsonData: any) {
+ this.openChannels.add(jsonData.channel)
+ this.sendQueuedSubscriptions(jsonData.channel)
+ }
+
+ private notifyListeners(jsonData: any) {
+ this.dataListeners.forEach(listener => listener(jsonData))
+ }
+
+ private handleMessageReceived(data: string) {
+ const messageData = _.get(data, 'data', data)
+ const jsonData = JSON.parse(messageData)
+ switch (jsonData.type) {
+ case 'AUTH_STATE':
+ this.handleAuthStateMessage(jsonData)
+ break
+ case 'CHANNEL_OPENED':
+ this.handleChannelOpened(jsonData)
+ break
+ case 'FEED_DATA':
+ this.notifyListeners(jsonData)
+ break
+ }
+ }
+
+ private sendMessage(json: object) {
+ if (_.isNil(this.webSocket)) {
+ return
+ }
+
+ this.webSocket.send(JSON.stringify(json))
+ }
+}
\ No newline at end of file
diff --git a/lib/quote-streamer.ts b/lib/quote-streamer.ts
deleted file mode 100644
index e299379..0000000
--- a/lib/quote-streamer.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import Feed, { EventType, IEvent } from '@dxfeed/api'
-import _ from 'lodash'
-
-export const SupportedEventTypes = [
- EventType.Quote,
- EventType.Trade,
- EventType.Summary,
- EventType.Greeks,
- EventType.Profile
-]
-
-export default class QuoteStreamer {
- private feed: Feed | null = null
-
- constructor(private readonly token: string, private readonly url: string) {}
-
- connect() {
- this.feed = new Feed()
- this.feed.setAuthToken(this.token)
- this.feed.connect(this.url)
- }
-
- disconnect() {
- if (!_.isNil(this.feed)) {
- this.feed.disconnect()
- }
- }
-
- subscribe(dxfeedSymbol: string, eventHandler: (event: IEvent) => void): () => void {
- if (_.isNil(this.feed)) {
- return _.noop
- }
-
- return this.feed.subscribe(
- SupportedEventTypes,
- [dxfeedSymbol],
- eventHandler
- )
- }
-}
diff --git a/lib/services/accounts-and-customers-service.ts b/lib/services/accounts-and-customers-service.ts
index 1a6de8d..940c07a 100644
--- a/lib/services/accounts-and-customers-service.ts
+++ b/lib/services/accounts-and-customers-service.ts
@@ -26,10 +26,9 @@ export default class AccountsAndCustomersService {
return extractResponseData(fullCustomerAccountResource)
}
- //Quote-streamer-tokens: Operations about quote-streamer-tokens
- async getQuoteStreamerTokens(){
- //Returns the appropriate quote streamer endpoint, level and identification token for the current customer to receive market data.
- const quoteStreamerTokens = (await this.httpClient.getData('/quote-streamer-tokens', {}, {}))
- return extractResponseData(quoteStreamerTokens)
+ //Returns the appropriate quote streamer endpoint, level and identification token for the current customer to receive market data.
+ async getApiQuoteToken() {
+ const apiQuoteToken = (await this.httpClient.getData('/api-quote-tokens', {}, {}))
+ return extractResponseData(apiQuoteToken)
}
}
diff --git a/lib/tastytrade-api.ts b/lib/tastytrade-api.ts
index f4920bf..f68664d 100644
--- a/lib/tastytrade-api.ts
+++ b/lib/tastytrade-api.ts
@@ -1,6 +1,6 @@
import TastytradeHttpClient from "./services/tastytrade-http-client"
import { AccountStreamer, STREAMER_STATE, Disposer, StreamerStateObserver } from './account-streamer'
-import QuoteStreamer from "./quote-streamer"
+import MarketDataStreamer, { MarketDataSubscriptionType, MarketDataListener } from "./market-data-streamer"
//Services:
import SessionService from "./services/session-service"
@@ -61,5 +61,5 @@ export default class TastytradeClient {
}
}
-export { QuoteStreamer }
+export { MarketDataStreamer, MarketDataSubscriptionType, MarketDataListener }
export { AccountStreamer, STREAMER_STATE, Disposer, StreamerStateObserver }
diff --git a/package-lock.json b/package-lock.json
index 3998ae5..dde9d74 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,16 +9,19 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
- "@dxfeed/api": "^1.1.0",
"@types/lodash": "^4.14.182",
"@types/qs": "^6.9.7",
"axios": "^1.3.4",
+ "isomorphic-ws": "^5.0.0",
"lodash": "^4.17.21",
- "qs": "^6.11.1"
+ "qs": "^6.11.1",
+ "uuid": "^9.0.0",
+ "ws": "^8.13.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "17.0.27",
+ "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"dotenv": "^16.0.3",
@@ -654,15 +657,6 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
- "node_modules/@dxfeed/api": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@dxfeed/api/-/api-1.1.0.tgz",
- "integrity": "sha512-U8agkpCaVXCQF6DZab0pXou8jQnOcN9ApriRnpwib3Jp0AQOzO8F8047yr/iCvTij9/XovcQyRUyWQaVudS1pw==",
- "dependencies": {
- "@types/cometd": "^4.0.7",
- "cometd": "^5.0.1"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1263,11 +1257,6 @@
"@babel/types": "^7.3.0"
}
},
- "node_modules/@types/cometd": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/@types/cometd/-/cometd-4.0.11.tgz",
- "integrity": "sha512-bn27WenWkEyCH1ynpZZdSA6gnYW30bLWVUIu+EluxEaFNDPor6EnVJtTCVDX/wzUdriaXRcIYjOTnIP1fEuGeQ=="
- },
"node_modules/@types/graceful-fs": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
@@ -1351,6 +1340,12 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true
},
+ "node_modules/@types/uuid": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz",
+ "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==",
+ "dev": true
+ },
"node_modules/@types/yargs": {
"version": "17.0.24",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
@@ -2032,11 +2027,6 @@
"node": ">= 0.8"
}
},
- "node_modules/cometd": {
- "version": "5.0.15",
- "resolved": "https://registry.npmjs.org/cometd/-/cometd-5.0.15.tgz",
- "integrity": "sha512-zEFBRuE1Roa5C4rJGNYUnT7AuKiYIpsWDbbBNo16vpOfMoSOY2Y+zANBxHpChTwytc7crAclBh8fNqfja/DAUw=="
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2942,6 +2932,14 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/isomorphic-ws": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
+ "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
+ "peerDependencies": {
+ "ws": "*"
+ }
+ },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
@@ -4732,6 +4730,14 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -4827,6 +4833,26 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -5357,15 +5383,6 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
- "@dxfeed/api": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@dxfeed/api/-/api-1.1.0.tgz",
- "integrity": "sha512-U8agkpCaVXCQF6DZab0pXou8jQnOcN9ApriRnpwib3Jp0AQOzO8F8047yr/iCvTij9/XovcQyRUyWQaVudS1pw==",
- "requires": {
- "@types/cometd": "^4.0.7",
- "cometd": "^5.0.1"
- }
- },
"@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -5851,11 +5868,6 @@
"@babel/types": "^7.3.0"
}
},
- "@types/cometd": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/@types/cometd/-/cometd-4.0.11.tgz",
- "integrity": "sha512-bn27WenWkEyCH1ynpZZdSA6gnYW30bLWVUIu+EluxEaFNDPor6EnVJtTCVDX/wzUdriaXRcIYjOTnIP1fEuGeQ=="
- },
"@types/graceful-fs": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
@@ -5939,6 +5951,12 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true
},
+ "@types/uuid": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz",
+ "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==",
+ "dev": true
+ },
"@types/yargs": {
"version": "17.0.24",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
@@ -6397,11 +6415,6 @@
"delayed-stream": "~1.0.0"
}
},
- "cometd": {
- "version": "5.0.15",
- "resolved": "https://registry.npmjs.org/cometd/-/cometd-5.0.15.tgz",
- "integrity": "sha512-zEFBRuE1Roa5C4rJGNYUnT7AuKiYIpsWDbbBNo16vpOfMoSOY2Y+zANBxHpChTwytc7crAclBh8fNqfja/DAUw=="
- },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -7059,6 +7072,12 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "isomorphic-ws": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
+ "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
+ "requires": {}
+ },
"istanbul-lib-coverage": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
@@ -8363,6 +8382,11 @@
"punycode": "^2.1.0"
}
},
+ "uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ },
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -8439,6 +8463,12 @@
"signal-exit": "^3.0.7"
}
},
+ "ws": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
+ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+ "requires": {}
+ },
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index 564f81d..a9d9dee 100644
--- a/package.json
+++ b/package.json
@@ -16,16 +16,19 @@
"postpack": "git tag -a $npm_package_version -m $npm_package_version && git push origin $npm_package_version"
},
"dependencies": {
- "@dxfeed/api": "^1.1.0",
"@types/lodash": "^4.14.182",
"@types/qs": "^6.9.7",
"axios": "^1.3.4",
+ "isomorphic-ws": "^5.0.0",
"lodash": "^4.17.21",
- "qs": "^6.11.1"
+ "qs": "^6.11.1",
+ "uuid": "^9.0.0",
+ "ws": "^8.13.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "17.0.27",
+ "@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"dotenv": "^16.0.3",
diff --git a/tests/integration/service/accounts-and-customers-service.test.ts b/tests/integration/service/accounts-and-customers-service.test.ts
index e1f8f81..b9b1133 100644
--- a/tests/integration/service/accounts-and-customers-service.test.ts
+++ b/tests/integration/service/accounts-and-customers-service.test.ts
@@ -46,9 +46,9 @@ describe('getFullCustomerAccountResource', () => {
})
})
-describe('getQuoteStreamerTokens', () => {
+describe('getApiQuoteToken', () => {
it('responds with the correct data', async function() {
- const response = await accountsAndCustomersService.getQuoteStreamerTokens()
+ const response = await accountsAndCustomersService.getApiQuoteToken()
expect(response.token).toBeDefined()
})
})