diff --git a/package.json b/package.json index 2716b5f4..af184ec9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "src", - "version": "0.8.0+72.1", + "version": "0.8.0+72.2", "private": true, "scripts": { "dev": "next dev", diff --git a/src/components/candlestick_chart/candlestick_chart.tsx b/src/components/candlestick_chart/candlestick_chart.tsx index fe452e15..4b3a044c 100644 --- a/src/components/candlestick_chart/candlestick_chart.tsx +++ b/src/components/candlestick_chart/candlestick_chart.tsx @@ -29,8 +29,11 @@ import {MarketContext} from '../../contexts/market_context'; import {ICandlestickData} from '../../interfaces/tidebit_defi_background/candlestickData'; import useStateRef from 'react-usestateref'; import {TimeSpanUnion, getTime} from '../../constants/time_span_union'; -import {useMarketStore} from '../../contexts/market_store_context'; +import {useMarketStoreContext} from '../../contexts/market_store_context'; import {useStore} from 'zustand'; +import {createWorkerStore, useWorkerStoreContext} from '../../contexts/worker_store'; +import {APIName, Method} from '../../constants/api_request'; +import {IResult} from '../../interfaces/tidebit_defi_background/result'; interface ITradingChartGraphProps { candleSize: number; @@ -180,42 +183,89 @@ const toCandlestickData = (data: ICandlestickData): CandlestickData => { }; }; +// const marketStore = useMarketStore(); +// TODO: if marketStore is null, throw Alert (20231120 - Shirley) +// if (!marketStore) throw new Error('Missing BearContext.Provider in the tree'); + +// const [timeSpan, selectTimeSpanHandler] = useStore(marketStore, s => [ +// s.timeSpan, +// s.selectTimeSpanHandler, +// ]); + +// const subTimeSpan = marketStore.subscribe( +// (state, prev) => { +// // eslint-disable-next-line no-console +// console.log('subTimeSpan state', state, 'prev', prev); +// } +// // s => s.timeSpan, +// // timeSpan => { +// // console.log('timeSpan', timeSpan); +// // } +// ); + +// subTimeSpan(); + // Info: 從外面傳進來的參數: 1.timespan 2.style of chart (20231106 - Shirley) export default function CandlestickChart({ candleSize, candlestickChartWidth, }: ITradingChartGraphProps) { - const marketStore = useMarketStore(); - // TODO: if marketStore is null, throw Alert (20231120 - Shirley) - if (!marketStore) throw new Error('Missing BearContext.Provider in the tree'); - const [timeSpan, selectTimeSpanHandler] = useStore(marketStore, s => [ - s.timeSpan, - s.selectTimeSpanHandler, - ]); - - const subTimeSpan = marketStore.subscribe( - (state, prev) => { - // eslint-disable-next-line no-console - console.log('subTimeSpan state', state, 'prev', prev); - } - // s => s.timeSpan, - // timeSpan => { - // console.log('timeSpan', timeSpan); - // } + const marketCtx = useContext(MarketContext); + const [init, requestHandler] = useWorkerStoreContext(s => [s.init, s.requestHandler]); + + const [timeSpan, selectTimeSpanHandler, listCandlesticks, testFetch] = useMarketStoreContext( + s => [s.timeSpan, s.selectTimeSpanHandler, s.listCandlesticks, s.testFetch] ); - subTimeSpan(); + // useEffect(() => { + // (async () => { + // const rsT = await testFetch(); + // const rs = await listCandlesticks('ETH-USDT', { + // timeSpan: TimeSpanUnion._12h, + // }); + // const rsMarket = await marketCtx.listCandlesticks('ETH-USDT', { + // timeSpan: TimeSpanUnion._1d, + // }); + // // const fetchPure = await fetch(); + // // eslint-disable-next-line no-console + // console.log('in candlestick chart component, listCandlesticks (marketStore) testFetch', rsT); + + // // eslint-disable-next-line no-console + // console.log('in candlestick chart component, listCandlesticks (marketStore) IIFE', rs); - // eslint-disable-next-line no-console - console.log('timeSpan in CandlestickChart', timeSpan); + // // eslint-disable-next-line no-console + // console.log('in candlestick chart component, listCandlesticks rsMarket', rsMarket); + // })(); + // }, []); - setTimeout(() => { - selectTimeSpanHandler(TimeSpanUnion._30m); + useEffect(() => { + init(); + let result; + (async () => { + try { + result = (await requestHandler({ + name: APIName.GET_TICKER_STATIC, + method: Method.GET, + params: 'ETH-USDT', + })) as IResult; + } catch (error) { + // Deprecate: error handle (Tzuhan - 20230321) + // eslint-disable-next-line no-console + console.error(`getTickerStatic error`, error); + } + })(); // eslint-disable-next-line no-console - console.log('timespan after call handler in CandlestickChart', timeSpan); - }, 5000); + console.log('call init() in candlestick chart'); + }, []); - const marketCtx = useContext(MarketContext); + // // eslint-disable-next-line no-console + // console.log('timeSpan in CandlestickChart (useMarketStoreContext)', timeSpan); + + // setTimeout(() => { + // selectTimeSpanHandler(TimeSpanUnion._30m); + // // eslint-disable-next-line no-console + // console.log('timespan after call handler in CandlestickChart', timeSpan); + // }, 5000); const [ohlcInfo, setOhlcInfo] = useState({ open: 0, diff --git a/src/contexts/marekt_data_store.tsx b/src/contexts/marekt_data_store.tsx new file mode 100644 index 00000000..a37e071b --- /dev/null +++ b/src/contexts/marekt_data_store.tsx @@ -0,0 +1,18 @@ +import {create} from 'zustand'; +import {ICandlestickData} from '../interfaces/tidebit_defi_background/candlestickData'; + +interface MarketDataStore { + candlestickChartDataForStore: ICandlestickData[] | null; + setCandlestickChartDataForStore: (data: ICandlestickData[]) => void; + + bears: number; + increase: (by: number) => void; +} + +export const useMarketData = create()(set => ({ + bears: 0, + increase: by => set(state => ({bears: state.bears + by})), + + candlestickChartDataForStore: null, + setCandlestickChartDataForStore: data => set({candlestickChartDataForStore: data}), +})); diff --git a/src/contexts/market_store_context.tsx b/src/contexts/market_store_context.tsx index 5cf794eb..11623aa7 100644 --- a/src/contexts/market_store_context.tsx +++ b/src/contexts/market_store_context.tsx @@ -1,4 +1,4 @@ -import {createStore} from 'zustand'; +import {createStore, useStore} from 'zustand'; import {ITimeSpanUnion, TimeSpanUnion} from '../constants/time_span_union'; import React, {createContext, useCallback, useContext} from 'react'; import TradeBookInstance from '../lib/books/trade_book'; @@ -9,8 +9,8 @@ import { dummyTicker, toDummyTickers, } from '../interfaces/tidebit_defi_background/ticker_data'; -import {IResult} from '../interfaces/tidebit_defi_background/result'; -import {ICandlestickData} from '../interfaces/tidebit_defi_background/candlestickData'; +import {defaultResultFailed, IResult} from '../interfaces/tidebit_defi_background/result'; +import {ICandlestickData, ITrade} from '../interfaces/tidebit_defi_background/candlestickData'; import {UserContext} from './user_context'; import {NotificationContext} from './notification_context'; import {WorkerContext} from './worker_context'; @@ -28,6 +28,68 @@ import { IWebsiteReserve, dummyWebsiteReserve, } from '../interfaces/tidebit_defi_background/website_reserve'; +import {TideBitEvent} from '../constants/tidebit_event'; +import EventEmitter from 'events'; +import {isCustomError} from '../lib/custom_error'; +import {APIName, Method} from '../constants/api_request'; +import {Code, Reason} from '../constants/code'; +import {createWorkerStore, useWorkerStoreContext} from './worker_store'; + +// FIXME: use store instead +// const userCtx = useContext(UserContext); +// const notificationCtx = useContext(NotificationContext); + +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [isInit, setIsInit, isInitRef] = useState(false); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [selectedTicker, setSelectedTicker, selectedTickerRef] = useState(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [cryptocurrencies, setCryptocurrencies, cryptocurrenciesRef] = useState( +// [] +// ); +// const [ +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// guaranteedStopFeePercentage, +// setGuaranteedStopFeePercentage, +// guaranteedStopFeePercentageRef, +// ] = useState(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [depositCryptocurrencies, setDepositCryptocurrencies, depositCryptocurrenciesRef] = useState< +// ICryptocurrency[] +// >([...dummyCryptocurrencies]); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [withdrawCryptocurrencies, setWithdrawCryptocurrencies, withdrawCryptocurrenciesRef] = +// useState([...dummyCryptocurrencies]); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [tickerStatic, setTickerStatic, tickerStaticRef] = useState(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [tickerLiveStatistics, setTickerLiveStatistics, tickerLiveStatisticsRef] = +// useState(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [candlestickChartData, setCandlestickChartData, candlestickChartDataRef] = useState< +// ICandlestickData[] | null +// >(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [candlestickInterval, setCandlestickInterval, candlestickIntervalRef] = useState< +// number | null +// >(null); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// // const [timeSpan, setTimeSpan, timeSpanRef] = useState(tickerBook.timeSpan); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [availableTickers, setAvailableTickers, availableTickersRef] = useState<{ +// [instId: string]: ITickerData; +// }>(toDummyTickers); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [isCFDTradable, setIsCFDTradable] = useState(false); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [candlestickId, setCandlestickId] = useState(''); // Deprecated: stale (20231019 - Shirley) +// /* ToDo: (20230419 - Julian) get TideBit data from backend */ +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [tidebitPromotion, setTidebitPromotion, tidebitPromotionRef] = +// useState(dummyTideBitPromotion); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const [websiteReserve, setWebsiteReserve, websiteReserveRef] = +// useState(dummyWebsiteReserve); interface MarketProps { // bears: number; @@ -35,8 +97,14 @@ interface MarketProps { timeSpan: ITimeSpanUnion; selectedTicker: ITickerData | null; selectedTickerRef: React.MutableRefObject; + availableTickers: {[instId: string]: ITickerData}; + tickerStatic: ITickerStatic | null; + tickerLiveStatistics: ITickerLiveStatistics | null; + tidebitPromotion: ITideBitPromotion; + websiteReserve: IWebsiteReserve; + guaranteedStopFeePercentage: number | null; - // candlestickChartData: ICandlestickData[] | null; + candlestickChartData: ICandlestickData[] | null; } const DEFAULT_PROPS: MarketProps = { @@ -45,8 +113,14 @@ const DEFAULT_PROPS: MarketProps = { timeSpan: TimeSpanUnion._1s, selectedTicker: dummyTicker, selectedTickerRef: React.createRef(), + availableTickers: {}, + tickerStatic: null, + tickerLiveStatistics: null, + tidebitPromotion: dummyTideBitPromotion, + websiteReserve: dummyWebsiteReserve, + guaranteedStopFeePercentage: null, - // candlestickChartData: [], + candlestickChartData: [], }; interface MarketState extends MarketProps { @@ -54,6 +128,20 @@ interface MarketState extends MarketProps { selectTimeSpanHandler: (props: ITimeSpanUnion) => void; // selectTickerHandler: (instId: string) => Promise; + init: () => Promise; + + setCandlestickChartData: (data: ICandlestickData[] | null) => void; + + listCandlesticks: ( + instId: string, + options: { + timeSpan: ITimeSpanUnion; + begin?: number; + end?: number; + limit?: number; + } + ) => Promise; + testFetch: () => Promise; } type MarketStore = ReturnType; @@ -61,64 +149,26 @@ type MarketStore = ReturnType; export const createMarketStore = (initProps?: Partial) => { const tickerBook = React.useMemo(() => TickerBookInstance, []); const tradeBook = React.useMemo(() => TradeBookInstance, []); + // const [init] = createWorkerStore(s => [s.init]); - // FIXME: use store instead - const userCtx = useContext(UserContext); - const notificationCtx = useContext(NotificationContext); - const workerCtx = useContext(WorkerContext); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [isInit, setIsInit, isInitRef] = useState(false); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [selectedTicker, setSelectedTicker, selectedTickerRef] = useState(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [cryptocurrencies, setCryptocurrencies, cryptocurrenciesRef] = useState( - [] - ); - const [ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - guaranteedStopFeePercentage, - setGuaranteedStopFeePercentage, - guaranteedStopFeePercentageRef, - ] = useState(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [depositCryptocurrencies, setDepositCryptocurrencies, depositCryptocurrenciesRef] = - useState([...dummyCryptocurrencies]); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [withdrawCryptocurrencies, setWithdrawCryptocurrencies, withdrawCryptocurrenciesRef] = - useState([...dummyCryptocurrencies]); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [tickerStatic, setTickerStatic, tickerStaticRef] = useState(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [tickerLiveStatistics, setTickerLiveStatistics, tickerLiveStatisticsRef] = - useState(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [candlestickChartData, setCandlestickChartData, candlestickChartDataRef] = useState< - ICandlestickData[] | null - >(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [candlestickInterval, setCandlestickInterval, candlestickIntervalRef] = useState< - number | null - >(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // const [timeSpan, setTimeSpan, timeSpanRef] = useState(tickerBook.timeSpan); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [availableTickers, setAvailableTickers, availableTickersRef] = useState<{ - [instId: string]: ITickerData; - }>(toDummyTickers); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [isCFDTradable, setIsCFDTradable] = useState(false); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [candlestickId, setCandlestickId] = useState(''); // Deprecated: stale (20231019 - Shirley) - /* ToDo: (20230419 - Julian) get TideBit data from backend */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [tidebitPromotion, setTidebitPromotion, tidebitPromotionRef] = - useState(dummyTideBitPromotion); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [websiteReserve, setWebsiteReserve, websiteReserveRef] = - useState(dummyWebsiteReserve); - - // const selectTimeSpanHandler + // const [init, requestHandler] = useWorkerStoreContext(s => [s.init, s.requestHandler]); + + // (async () => { + // await init(); + // let result; + // try { + // result = (await requestHandler({ + // name: APIName.GET_TICKER_STATIC, + // method: Method.GET, + // params: 'ETH-USDT', + // })) as IResult; + // } catch (error) { + // // eslint-disable-next-line no-console + // console.error(`getTickerStatic error in marektStore`, error); + // } + + // console.log('result', result); + // })(); return createStore()((set, get) => ({ ...DEFAULT_PROPS, @@ -127,6 +177,21 @@ export const createMarketStore = (initProps?: Partial) => { // timeSpan: timeSpan, timeSpan: TimeSpanUnion._1s, + // FIXME: init to put another place + init: async () => { + const listCandlestick = await get().listCandlesticks('eth-usdt', { + timeSpan: TimeSpanUnion._12h, + }); + // eslint-disable-next-line no-console + console.log('init called listCandlestick', listCandlestick); + const rs = await Promise.resolve(); + return rs; + }, + + setCandlestickChartData(data) { + set(state => ({...state, candlestickChartData: data})); + }, + selectTimeSpanHandler: (timeSpan: ITimeSpanUnion, instId?: string) => { // eslint-disable-next-line no-console console.log('in selectTimeSpanHandler timeSpan INPUT: ', timeSpan); @@ -141,48 +206,98 @@ export const createMarketStore = (initProps?: Partial) => { tickerBook.timeSpan = updatedTimeSpan; // setTimeSpan(tickerBook.timeSpan); - - // console.log('after setTimeSpan timeSpan: ', timeSpanRef.current); set(state => ({...state, timeSpan: updatedTimeSpan})); - // eslint-disable-next-line no-console - console.log( - 'use set in selectTimeSpanHandler timeSpan INPUT: ', - timeSpan, - 'get().timeSpan: ', - get().timeSpan - ); // syncCandlestickData(selectedTickerRef.current?.instId ?? DEFAULT_INSTID, updatedTimeSpan); }, - // selectTickerHandlerasync: (instId: string) => { - // if (!instId) return {...defaultResultFailed}; - // const ticker: ITickerData = availableTickersRef.current[instId]; - // if (!ticker) return {...defaultResultFailed}; - // notificationCtx.emitter.emit(TideBitEvent.CHANGE_TICKER, ticker); - // setTickerLiveStatistics(null); - // setTickerStatic(null); - // setSelectedTicker(ticker); - // await listMarketTrades(ticker.instId); - // selectTimeSpanHandler(timeSpanRef.current, ticker.instId); - // // ++ TODO: get from api - // const getTickerStaticResult = await getTickerStatic(ticker.instId); - // if (getTickerStaticResult?.success) - // setTickerStatic(getTickerStaticResult.data as ITickerStatic); - // const getTickerLiveStatisticsResult = await getTickerLiveStatistics(ticker.instId); - // if (getTickerLiveStatisticsResult?.success) - // setTickerLiveStatistics(getTickerLiveStatisticsResult.data as ITickerLiveStatistics); - // notificationCtx.emitter.emit(TideBitEvent.TICKER_CHANGE, ticker); - // return {...defaultResultSuccess}; - // }, + testFetch: async () => { + const rs = await fetch( + 'https://api.tidebit-defi.com/api/v1/candlesticks/ETH-USDT?timeSpan=1h&limit=50', + { + mode: 'cors', + headers: { + 'Access-Control-Allow-Origin': '*', + }, + } + ); + + const data = await rs.json(); + + // eslint-disable-next-line no-console + console.log('data in testFetch', data); + return data; + }, + + listCandlesticks: async ( + instId: string, + options: { + begin?: number; // Info: in milliseconds (20230530 - tzuhan) + end?: number; // Info: in milliseconds (20230530 - tzuhan) + timeSpan: ITimeSpanUnion; + limit?: number; + } + ) => { + // eslint-disable-next-line no-console + console.log('listCandlesticks called in MarketStore func'); + + let result: IResult = {...defaultResultFailed}; + try { + // result = (await workerCtx.requestHandler({ + // name: APIName.LIST_CANDLESTICKS, + // method: Method.GET, + // params: instId, + // query: {...options}, + // })) as IResult; + // if (result.success) { + // // Info: call API 拿到資料 + // // const candlesticks = result.data as IInstCandlestick; + // } + // eslint-disable-next-line no-console + console.log( + 'listCandlesticks(store) instId: ', + instId, + 'options: ', + options, + 'result: ', + result + ); + } catch (error) { + result = { + success: false, + code: isCustomError(error) ? error.code : Code.INTERNAL_SERVER_ERROR, + reason: isCustomError(error) + ? Reason[error.code] + : (error as Error)?.message || Reason[Code.INTERNAL_SERVER_ERROR], + }; + + // eslint-disable-next-line no-console + console.log( + 'listCandlesticks(store) instId catch error: ', + instId, + 'options: ', + options, + 'error: ', + error + ); + } + return result; + }, })); }; export const MarketStoreContext = createContext(null); -export const useMarketStore = () => { - const context = useContext(MarketStoreContext); - // Info: If not in a provider, it still reveals `createContext` data, meaning it'll never be falsy. +// export const useMarketStore = () => { +// const context = useContext(MarketStoreContext); +// // Info: If not in a provider, it still reveals `createContext` data, meaning it'll never be falsy. - return context; -}; +// return context; +// }; + +// FIXME: folder structure and `CustomError` +export function useMarketStoreContext(selector: (state: MarketState) => T): T { + const store = useContext(MarketStoreContext); + if (!store) throw new Error('Missing MarketStoreContext.Provider in the tree'); + return useStore(store, selector); +} diff --git a/src/contexts/worker_store.tsx b/src/contexts/worker_store.tsx new file mode 100644 index 00000000..b0f19c6a --- /dev/null +++ b/src/contexts/worker_store.tsx @@ -0,0 +1,434 @@ +import {createStore, useStore} from 'zustand'; +import Pusher, {Channel} from 'pusher-js'; +import keccak from '@cafeca/keccak'; +import {formatAPIRequest, FormatedTypeRequest, TypeRequest} from '../constants/api_request'; +import {Events} from '../constants/events'; +import {TideBitEvent} from '../constants/tidebit_event'; +import {ITickerData} from '../interfaces/tidebit_defi_background/ticker_data'; +import { + IPusherData, + IPusherPrivateData, + PusherChannel, +} from '../interfaces/tidebit_defi_background/pusher_data'; +import {ITrade} from '../interfaces/tidebit_defi_background/candlestickData'; +import {getCookieByName} from '../lib/common'; +import {createContext, useContext, useRef} from 'react'; +import {NotificationContext} from './notification_context'; +import useState from 'react-usestateref'; + +type IJobType = 'API' | 'WS'; +export interface IJobTypeConstant { + API: IJobType; + WS: IJobType; +} +export const JobType: IJobTypeConstant = { + API: 'API', + WS: 'WS', +}; + +interface WorkerProps { + // Define the state variables + apiWorker: Worker | null; + pusher: Pusher | null; + publicChannel: Channel | null; + socketId: string | null; + // Other state variables... +} + +const DEFAULT_PROPS: WorkerProps = { + apiWorker: null, + pusher: null, + publicChannel: null, + socketId: null, +}; + +interface WorkerState extends WorkerProps { + // Define the actions + init: () => Promise; + requestHandler: (data: TypeRequest) => Promise; + subscribeUser: (address: string) => void; + // Other actions... +} + +type WorkerStore = ReturnType; + +let jobTimer: NodeJS.Timeout | null = null; + +export const createWorkerStore = (initProps?: Partial) => { + const pusherKey = process.env.PUSHER_APP_KEY ?? ''; + const pusherHost = process.env.PUSHER_HOST ?? ''; + const pusherPort = +(process.env.PUSHER_PORT ?? '0'); + const notificationCtx = useContext(NotificationContext); + + // const apiWorker = useRef(null); + // const pusher = useRef(null); + // const publicChannel = useRef(null); + // const socketId = useRef(null); + + // const jobQueueOfWS = useRef<((...args: []) => Promise)[]>([]); + // const jobQueueOfAPI = useRef<((...args: []) => Promise)[]>([]); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [apiWorker, setAPIWorker, apiWorkerRef] = useState(null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [pusher, setPuser, pusherRef] = useState(null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [publicChannel, setPublicChannel, publicChannelRef] = useState(null); + const jobQueueOfWS = useRef<((...args: []) => Promise)[]>([]); + const jobQueueOfAPI = useRef<((...args: []) => Promise)[]>([]); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [socketId, setSocketId, socketIdRef] = useState(null); + + // const _apiWorker = async () => { + // // eslint-disable-next-line no-console + // console.log('API worker called'); + // const job = jobQueueOfAPI.current.shift(); + // if (job) { + // await job(); + // await _apiWorker(); + // } else { + // if (jobTimer) clearTimeout(jobTimer); + // jobTimer = setTimeout(() => _apiWorker(), 1000); + // } + // }; + + // const createJob = (type: IJobType, callback: () => Promise) => { + // const job = () => { + // return new Promise(async (resolve, reject) => { + // try { + // await callback(); + // resolve(); + // } catch { + // reject(); + // } + // }); + // }; + // switch (type) { + // case JobType.API: + // jobQueueOfAPI.current = [...jobQueueOfAPI.current, job]; + // break; + // case JobType.WS: + // jobQueueOfWS.current = [...jobQueueOfWS.current, job]; + // break; + // default: + // break; + // } + // }; + + const apiInit = () => { + const apiWorker = new Worker(new URL('../lib/workers/api.worker.ts', import.meta.url)); + setAPIWorker(apiWorker); + /* Deprecated: callback in requestHandler (Tzuhan - 20230420) + apiWorker.onmessage = event => { + const {name, result, error} = event.data; + requests.current[name]?.callback(result, error); + delete requests.current[name]; + }; + */ + }; + + const subscribeTickers = () => { + if (pusherRef.current) { + const channel = pusherRef.current.subscribe(PusherChannel.GLOBAL_CHANNEL); + channel.bind(Events.TICKERS, (pusherData: IPusherData) => { + const tickerData = pusherData as ITickerData; + notificationCtx.emitter.emit(TideBitEvent.TICKER, tickerData); + }); + } + }; + + const subscribeTrades = () => { + if (publicChannelRef.current) { + publicChannelRef.current.bind(Events.TRADES, (pusherData: IPusherData) => { + const trade = pusherData as ITrade; + notificationCtx.emitter.emit(TideBitEvent.TRADES, trade); + }); + } + }; + + const subscribeUser = (address: string) => { + if (pusherRef.current) { + const channelName = `${PusherChannel.PRIVATE_CHANNEL}-${keccak + .keccak256(address.toLowerCase().replace(`0x`, ``)) + .slice(0, 8)}`; + const channel = pusherRef.current?.subscribe(channelName); + channel.bind(Events.BALANCE, (data: IPusherPrivateData) => { + notificationCtx.emitter.emit(Events.BALANCE, data); + }); + channel.bind(Events.CFD, (data: IPusherPrivateData) => { + notificationCtx.emitter.emit(Events.CFD, data); + }); + channel.bind(Events.BOLT_TRANSACTION, (data: IPusherPrivateData) => { + notificationCtx.emitter.emit(Events.BOLT_TRANSACTION, data); + }); + channel.bind(Events.ASSETS, (data: IPusherPrivateData) => { + notificationCtx.emitter.emit(Events.ASSETS, data); + }); + } + }; + + const pusherInit = () => { + const pusher = new Pusher(pusherKey, { + cluster: '', + wsHost: pusherHost, + wsPort: pusherPort, + channelAuthorization: { + transport: 'jsonp', + endpoint: `${pusherHost}/api/pusher/auth`, + headers: { + deWT: getCookieByName('DeWT'), + }, + params: { + deWT: getCookieByName('DeWT'), + }, + }, + }); + setPuser(pusher); + pusher.connection.bind('connected', function () { + const socketId = pusher.connection.socket_id; + setSocketId(socketId); + const channel = pusherRef.current?.subscribe(PusherChannel.GLOBAL_CHANNEL); + if (channel) { + setPublicChannel(channel); + subscribeTickers(); + subscribeTrades(); + } + }); + }; + + const init = async () => { + apiInit(); + pusherInit(); + await _apiWorker(); + }; + + const _apiWorker = async () => { + const job = jobQueueOfAPI.current.shift(); + if (job) { + await job(); + await _apiWorker(); + } else { + if (jobTimer) clearTimeout(jobTimer); + jobTimer = setTimeout(() => _apiWorker(), 1000); + } + }; + + const createJob = (type: IJobType, callback: () => Promise) => { + const job = () => { + return new Promise(async (resolve, reject) => { + try { + await callback(); + resolve(); + } catch { + reject(); + } + }); + }; + switch (type) { + case JobType.API: + jobQueueOfAPI.current = [...jobQueueOfAPI.current, job]; + break; + case JobType.WS: + jobQueueOfWS.current = [...jobQueueOfWS.current, job]; + break; + default: + break; + } + }; + + const requestHandler = async (data: TypeRequest) => { + // eslint-disable-next-line no-console + console.log('requestHandler called', data); + const apiWorker = apiWorkerRef.current; + + if (apiWorker) { + const request: FormatedTypeRequest = formatAPIRequest(data); + const promise = new Promise((resolve, reject) => { + apiWorker.onmessage = event => { + const {name, result, error} = event.data; + if (name === request.name) { + if (error) reject(error); + else resolve(result); + } + }; + }); + apiWorkerRef.current.postMessage(request.request); + return promise; + } else { + createJob(JobType.API, () => requestHandler(data)); + } + }; + + return createStore()((set, get) => ({ + ...DEFAULT_PROPS, + ...initProps, + + // Actions + init, + requestHandler: requestHandler, + subscribeUser, + })); + + // return createStore()((set, get) => ({ + // ...DEFAULT_PROPS, + // ...initProps, + + // // Actions + // init: async () => { + // // Initialize API Worker + // const apiWorker = new Worker(new URL('../lib/workers/api.worker.ts', import.meta.url)); + // set({apiWorker}); + + // console.log('apiWorker get()', get().apiWorker, 'apiWorker', apiWorker); + + // // Initialize Pusher + // const pusher = new Pusher(pusherKey, { + // cluster: '', + // wsHost: pusherHost, + // wsPort: pusherPort, + // channelAuthorization: { + // transport: 'jsonp', + // endpoint: `${pusherHost}/api/pusher/auth`, + // headers: { + // deWT: getCookieByName('DeWT'), + // }, + // params: { + // deWT: getCookieByName('DeWT'), + // }, + // }, + // }); + // set({pusher}); + + // await _apiWorker(); + // }, + + // subscribeTickers: () => { + // // Info: subscribeTickers(); + // if (get().pusher) { + // const pusher = get().pusher; + // const channel = pusher?.subscribe(PusherChannel.GLOBAL_CHANNEL); + // if (!channel) return; + // channel.bind(Events.TICKERS, (pusherData: IPusherData) => { + // const tickerData = pusherData as ITickerData; + + // notificationCtx.emitter.emit(TideBitEvent.TICKER, tickerData); + // }); + // } + // }, + + // subscribeTrades: () => { + // const pusher = get().pusher; + // pusher.connection.bind('connected', function () { + // const socketId = pusher.connection.socket_id; + // set({socketId}); + // const channel = get().pusher?.subscribe(PusherChannel.GLOBAL_CHANNEL); + // if (channel) { + // set({publicChannel: channel}); + + // // Info: subscribeTrades(); + // if (get().publicChannel) { + // const publicChannel = get().publicChannel; + // if (!publicChannel) return; + // publicChannel.bind(Events.TRADES, (pusherData: IPusherData) => { + // const trade = pusherData as ITrade; + + // notificationCtx.emitter.emit(TideBitEvent.TRADES, trade); + // }); + // } + // } + // }); + // }, + + // requestHandler: async (data: TypeRequest) => { + // console.log('requestHandler called', data); + // const apiWorker = get().apiWorker; + + // if (apiWorker) { + // const request: FormatedTypeRequest = formatAPIRequest(data); + // const promise = new Promise((resolve, reject) => { + // apiWorker.onmessage = event => { + // const {name, result, error} = event.data; + // if (name === request.name) { + // if (error) reject(error); + // else resolve(result); + // } + // }; + // }); + // apiWorker.postMessage(request.request); + // return promise; + // } else { + // createJob(JobType.API, () => get().requestHandler(data)); + // } + // }, + + // subscribeUser: (address: string) => { + // // User subscription logic + // // Similar logic to your original subscribeUser + // const pusher = get().pusher; + // if (pusher) { + // const channelName = `${PusherChannel.PRIVATE_CHANNEL}-${keccak + // .keccak256(address.toLowerCase().replace(`0x`, ``)) + // .slice(0, 8)}`; + // const channel = pusher?.subscribe(channelName); + // channel.bind(Events.BALANCE, (data: IPusherPrivateData) => { + // notificationCtx.emitter.emit(Events.BALANCE, data); + // }); + // channel.bind(Events.CFD, (data: IPusherPrivateData) => { + // notificationCtx.emitter.emit(Events.CFD, data); + // }); + // channel.bind(Events.BOLT_TRANSACTION, (data: IPusherPrivateData) => { + // notificationCtx.emitter.emit(Events.BOLT_TRANSACTION, data); + // }); + // channel.bind(Events.ASSETS, (data: IPusherPrivateData) => { + // notificationCtx.emitter.emit(Events.ASSETS, data); + // }); + // } + // }, + + // pusherInit: () => { + // const pusher = new Pusher(pusherKey, { + // cluster: '', + // wsHost: pusherHost, + // wsPort: pusherPort, + // channelAuthorization: { + // transport: 'jsonp', + // endpoint: `${pusherHost}/api/pusher/auth`, + // headers: { + // deWT: getCookieByName('DeWT'), + // }, + // params: { + // deWT: getCookieByName('DeWT'), + // }, + // }, + // }); + // set({pusher}); + // pusher.connection.bind('connected', function () { + // const socketId = pusher.connection.socket_id; + // set({socketId}); + // const pusherGet = get().pusher; + // const channel = pusherGet?.subscribe(PusherChannel.GLOBAL_CHANNEL); + // if (channel) { + // set({publicChannel}); + // subscribeTickers(); + // subscribeTrades(); + // } + // }); + // }, + // })); +}; + +export const WorkerStoreContext = createContext(null); + +// export const useMarketStore = () => { +// const context = useContext(MarketStoreContext); +// // Info: If not in a provider, it still reveals `createContext` data, meaning it'll never be falsy. + +// return context; +// }; + +// FIXME: folder structure and `CustomError` +export function useWorkerStoreContext(selector: (state: WorkerState) => T): T { + const store = useContext(WorkerStoreContext); + if (!store) throw new Error('Missing WorkerStoreContext.Provider in the tree'); + return useStore(store, selector); +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 338fd395..132daab6 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,11 +11,26 @@ import {GlobalProvider} from '../contexts/global_context'; import {NotificationProvider} from '../contexts/notification_context'; import {AppProvider} from '../contexts/app_context'; import {WorkerProvider} from '../contexts/worker_context'; -import React, {useRef} from 'react'; +import React, {useContext, useEffect, useRef} from 'react'; import {MarketStoreContext, createMarketStore} from '../contexts/market_store_context'; +import {useStore} from 'zustand'; +import {WorkerStoreContext, createWorkerStore} from '../contexts/worker_store'; function MyApp({Component, pageProps}: AppProps) { const marketStore = useRef(createMarketStore()).current; + const workerStore = useRef(createWorkerStore()).current; + + // const marketStore1 = useMarketStore(); + // TODO: if marketStore is null, throw Alert (20231120 - Shirley) + // if (!marketStore) throw new Error('Missing BearContext.Provider in the tree'); + // const [timeSpan, selectTimeSpanHandler, init] = useStore(marketStore, s => [ + // s.timeSpan, + // s.selectTimeSpanHandler, + // s.init, + // ]); + + // init(); + return ( <>
@@ -25,9 +40,11 @@ function MyApp({Component, pageProps}: AppProps) { - - - + + + + + diff --git a/src/pages/trial.tsx b/src/pages/trial.tsx index 2cd1d9dd..4465e6f4 100644 --- a/src/pages/trial.tsx +++ b/src/pages/trial.tsx @@ -1,8 +1,11 @@ import React, {useEffect} from 'react'; import {TimeSpanUnion} from '../constants/time_span_union'; import {useStore} from 'zustand'; -import {useMarketStore} from '../contexts/market_store_context'; +import {useMarketStoreContext} from '../contexts/market_store_context'; import {setInterval} from 'timers'; +import {useWorkerStoreContext} from '../contexts/worker_store'; +import {APIName, Method} from '../constants/api_request'; +import {IResult} from '../interfaces/tidebit_defi_background/result'; const pickRandomTimeSpan = () => { const timeSpans = Object.values(TimeSpanUnion); @@ -10,39 +13,58 @@ const pickRandomTimeSpan = () => { return timeSpans[randomIndex]; }; +// const marketStore = useMarketStore(); +// // TODO: if marketStore is null, throw Alert (20231120 - Shirley) +// if (!marketStore) throw new Error('Missing BearContext.Provider in the tree'); +// const [timeSpan, selectTimeSpanHandler] = useStore(marketStore, s => [ +// s.timeSpan, +// s.selectTimeSpanHandler, +// ]); + +// const subTimeSpan = marketStore.subscribe( +// (state, prev) => { +// // eslint-disable-next-line no-console +// console.log('subTimeSpan state', state, 'prev', prev); +// } +// // s => s.timeSpan, +// // timeSpan => { +// // console.log('timeSpan', timeSpan); +// // } +// ); + +// subTimeSpan(); + const Trial = () => { - const marketStore = useMarketStore(); - // TODO: if marketStore is null, throw Alert (20231120 - Shirley) - if (!marketStore) throw new Error('Missing BearContext.Provider in the tree'); - const [timeSpan, selectTimeSpanHandler] = useStore(marketStore, s => [ + const [timeSpan, selectTimeSpanHandler] = useMarketStoreContext(s => [ s.timeSpan, s.selectTimeSpanHandler, ]); - const subTimeSpan = marketStore.subscribe( - (state, prev) => { - // eslint-disable-next-line no-console - console.log('subTimeSpan state', state, 'prev', prev); - } - // s => s.timeSpan, - // timeSpan => { - // console.log('timeSpan', timeSpan); - // } - ); - - subTimeSpan(); - - // eslint-disable-next-line no-console - console.log('timeSpan in Trial', timeSpan); + const [init, requestHandler] = useWorkerStoreContext(s => [s.init, s.requestHandler]); useEffect(() => { const timeOut = setInterval(() => { const ran = pickRandomTimeSpan(); selectTimeSpanHandler(ran); - // eslint-disable-next-line no-console - console.log('timespan after call handler in CandlestickChart', timeSpan); }, 2000); + (async () => { + await init(); + let result; + try { + result = (await requestHandler({ + name: APIName.GET_TICKER_STATIC, + method: Method.GET, + params: 'ETH-USDT', + })) as IResult; + } catch (error) { + // eslint-disable-next-line no-console + console.error(`getTickerStatic error in trial`, error); + } + // eslint-disable-next-line no-console + console.log('result', result); + })(); + return () => { clearInterval(timeOut); };