vega-frontend-monorepo/apps/trading/data-providers/markets-data-provider.ts
2022-03-24 14:29:12 +01:00

167 lines
3.4 KiB
TypeScript

import { gql } from '@apollo/client';
import { produce } from 'immer';
import type { ApolloClient } from '@apollo/client';
import type { Subscription } from 'zen-observable-ts';
import {
Markets,
Markets_markets,
MarketDataSub,
MarketDataSub_marketData,
} from '@vegaprotocol/graphql';
const MARKET_DATA_FRAGMENT = gql`
fragment MarketDataFields on MarketData {
market {
id
state
tradingMode
}
bestBidPrice
bestOfferPrice
markPrice
}
`;
const MARKETS_QUERY = gql`
${MARKET_DATA_FRAGMENT}
query Markets {
markets {
id
name
decimalPlaces
data {
...MarketDataFields
}
tradableInstrument {
instrument {
code
product {
... on Future {
settlementAsset {
symbol
}
}
}
}
}
}
}
`;
const MARKET_DATA_SUB = gql`
${MARKET_DATA_FRAGMENT}
subscription MarketDataSub {
marketData {
...MarketDataFields
}
}
`;
export interface CallbackArg {
data?: Markets_markets[];
error?: Error;
loading: boolean;
delta?: MarketDataSub_marketData;
}
export interface Callback {
(arg: CallbackArg): void;
}
const callbacks: Callback[] = [];
const updateQueue: MarketDataSub_marketData[] = [];
let data: Markets_markets[] = undefined;
let error: Error = undefined;
let loading = false;
let client: ApolloClient<object> = undefined;
let subscription: Subscription = undefined;
const notify = (callback, delta?: MarketDataSub_marketData) => {
callback({
data,
error,
loading,
delta,
});
};
const notifyAll = (delta?: MarketDataSub_marketData) => {
callbacks.forEach((callback) => notify(callback, delta));
};
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
};
const initialize = async () => {
if (subscription) {
return;
}
loading = true;
error = null;
notifyAll();
subscription = client
.subscribe<MarketDataSub>({
query: MARKET_DATA_SUB,
})
.subscribe(({ data: delta }) => {
if (loading) {
updateQueue.push(delta.marketData);
} else {
data = produce(data, (draft) => {
update(draft, delta.marketData);
});
notifyAll(delta.marketData);
}
});
try {
const res = await client.query<Markets>({
query: MARKETS_QUERY,
});
data = res.data.markets;
if (updateQueue) {
data = produce(data, (draft) => {
while (updateQueue.length) {
update(draft, updateQueue.shift());
}
});
}
} catch (e) {
error = e;
subscription.unsubscribe();
subscription = undefined;
} finally {
loading = false;
notifyAll();
}
};
const unsubscribe = (callback: Callback) => {
callbacks.splice(callbacks.indexOf(callback), 1);
if (callbacks.length === 0) {
subscription.unsubscribe();
subscription = undefined;
data = undefined;
error = undefined;
loading = false;
}
};
export const subscribe = (c: ApolloClient<object>, callback) => {
if (!client) {
client = c;
}
callbacks.push(callback);
if (callbacks.length === 1) {
initialize();
} else {
notify(callback);
}
return () => unsubscribe(callback);
};