diff --git a/index.html b/index.html
index e3bdc0cb..130dd626 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Deriv V2
+ Deriv Bot
diff --git a/package-lock.json b/package-lock.json
index 2195baa2..0690f130 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -103,6 +103,7 @@
"ts-jest": "^29.1.2",
"typescript": "^5.2.2",
"vite-live-preview": "^0.1.6",
+ "vite-plugin-static-copy": "^1.0.5",
"vite-plugin-svgr": "^4.2.0",
"vite-require": "^0.2.3",
"vite-tsconfig-paths": "^4.3.2",
@@ -19205,6 +19206,24 @@
"magic-string": "^0.30.1"
}
},
+ "node_modules/vite-plugin-static-copy": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-1.0.5.tgz",
+ "integrity": "sha512-02k0Rox+buYdEOfeilKZSgs1gXfPf9RjVztZEIYZgVIxjsVZi6AXssjzdi+qW6zYt00d3bq+tpP2voVXN2fKLw==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": "^3.5.3",
+ "fast-glob": "^3.2.11",
+ "fs-extra": "^11.1.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0"
+ }
+ },
"node_modules/vite-plugin-svgr": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz",
diff --git a/package.json b/package.json
index 10724499..007a6957 100644
--- a/package.json
+++ b/package.json
@@ -113,6 +113,7 @@
"ts-jest": "^29.1.2",
"typescript": "^5.2.2",
"vite-live-preview": "^0.1.6",
+ "vite-plugin-static-copy": "^1.0.5",
"vite-plugin-svgr": "^4.2.0",
"vite-require": "^0.2.3",
"vite-tsconfig-paths": "^4.3.2",
diff --git a/src/app/app-content.jsx b/src/app/app-content.jsx
index 61bf5703..c6537584 100644
--- a/src/app/app-content.jsx
+++ b/src/app/app-content.jsx
@@ -1,8 +1,10 @@
import React from 'react';
import { ToastContainer } from 'react-toastify';
+import { setSmartChartsPublicPath } from '@deriv/deriv-charts';
import { Loader } from '@deriv-com/ui';
+import { getUrlBase } from '@/components/shared';
import TransactionDetailsModal from '@/components/transaction-details';
import { api_base, ApiHelpers, ServerTime } from '@/external/bot-skeleton';
import { useStore } from '@/hooks/useStore';
@@ -57,6 +59,10 @@ const AppContent = () => {
}
};
+ React.useEffect(() => {
+ setSmartChartsPublicPath(getUrlBase('/js/smartcharts/'));
+ }, []);
+
React.useEffect(() => {
// Listen for proposal open contract messages to check
// if there is any active contract from bot still running
diff --git a/src/components/trade-animation/trade-animation.tsx b/src/components/trade-animation/trade-animation.tsx
index be12bbca..b2257f4e 100644
--- a/src/components/trade-animation/trade-animation.tsx
+++ b/src/components/trade-animation/trade-animation.tsx
@@ -31,16 +31,15 @@ const TradeAnimation = observer(({ className, should_show_overlay }: TTradeAnima
onStopBotClick,
performSelfExclusionCheck,
} = run_panel;
- const { account_status } = client;
+ const { account_status, is_logged_in } = client;
const cashier_validation = account_status?.cashier_validation;
const [shouldDisable, setShouldDisable] = React.useState(false);
const is_unavailable_for_payment_agent = cashier_validation?.includes('WithdrawServiceUnavailableForPA');
// perform self-exclusion checks which will be stored under the self-exclusion-store
React.useEffect(() => {
- performSelfExclusionCheck();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ if (is_logged_in) performSelfExclusionCheck();
+ }, [is_logged_in, performSelfExclusionCheck]);
React.useEffect(() => {
if (shouldDisable) {
diff --git a/src/external/bot-skeleton/scratch/dbot.js b/src/external/bot-skeleton/scratch/dbot.js
index 6c628fc0..b733c90c 100644
--- a/src/external/bot-skeleton/scratch/dbot.js
+++ b/src/external/bot-skeleton/scratch/dbot.js
@@ -27,7 +27,7 @@ class DBot {
async initWorkspace(public_path, store, api_helpers_store, is_mobile, is_dark_mode) {
await loadBlockly(is_dark_mode);
const recent_files = await getSavedWorkspaces();
- api_base.init();
+
this.interpreter = Interpreter();
const that = this;
window.Blockly.Blocks.trade_definition_tradetype.onchange = function (event) {
diff --git a/src/external/bot-skeleton/services/api/active-symbols.js b/src/external/bot-skeleton/services/api/active-symbols.js
index 1d426d74..6ea60a4d 100644
--- a/src/external/bot-skeleton/services/api/active-symbols.js
+++ b/src/external/bot-skeleton/services/api/active-symbols.js
@@ -7,7 +7,7 @@ import PendingPromise from '../../utils/pending-promise';
import { api_base } from './api-base';
export default class ActiveSymbols {
- constructor(ws, trading_times) {
+ constructor(trading_times) {
this.active_symbols = [];
this.disabled_symbols = config.DISABLED_SYMBOLS;
this.disabled_submarkets = config.DISABLED_SUBMARKETS;
@@ -15,7 +15,6 @@ export default class ActiveSymbols {
this.is_initialised = false;
this.processed_symbols = {};
this.trading_times = trading_times;
- this.ws = ws;
}
async retrieveActiveSymbols(is_forced_update = false) {
@@ -27,9 +26,14 @@ export default class ActiveSymbols {
}
this.is_initialised = true;
- const active_symbols = api_base?.active_symbols ?? [];
- this.active_symbols = active_symbols;
+ if (api_base.has_active_symbols) {
+ this.active_symbols = api_base?.active_symbols ?? [];
+ } else {
+ await api_base.active_symbols_promise;
+ this.active_symbols = api_base?.active_symbols ?? [];
+ }
+
this.processed_symbols = this.processActiveSymbols();
// TODO: fix need to look into it as the method is not present
diff --git a/src/external/bot-skeleton/services/api/api-base.js b/src/external/bot-skeleton/services/api/api-base.js
index 9b8fdf64..6bc7e1dd 100644
--- a/src/external/bot-skeleton/services/api/api-base.js
+++ b/src/external/bot-skeleton/services/api/api-base.js
@@ -2,6 +2,7 @@ import { observer as globalObserver } from '../../utils/observer';
import { doUntilDone, socket_state } from '../tradeEngine/utils/helpers';
import { generateDerivApiInstance, getLoginId, getToken } from './appId';
+import chart_api from './chart-api';
class APIBase {
api;
@@ -12,26 +13,28 @@ class APIBase {
is_running = false;
subscriptions = [];
time_interval = null;
- has_activeSymbols = false;
+ has_active_symbols = false;
is_stopping = false;
active_symbols = [];
+ active_symbols_promise = null;
+
async init(force_update = false) {
+ this.toggleRunButton(true);
+ if (force_update) this.terminate();
+ this.api = generateDerivApiInstance();
+ if (!this.has_active_symbols) {
+ this.active_symbols_promise = this.getActiveSymbols();
+ }
+ this.initEventListeners();
+ if (this.time_interval) clearInterval(this.time_interval);
+ this.time_interval = null;
+ this.getTime();
+
if (getLoginId()) {
- this.toggleRunButton(true);
- if (force_update) this.terminate();
- this.api = generateDerivApiInstance();
- this.initEventListeners();
await this.authorizeAndSubscribe();
- if (this.time_interval) clearInterval(this.time_interval);
- this.time_interval = null;
- this.getTime();
- } else {
- this.api = generateDerivApiInstance();
- if (!this.has_activeSymbols) {
- this.getActiveSymbols();
- }
}
+ chart_api.init();
}
getConnectionStatus() {
@@ -79,10 +82,10 @@ class APIBase {
this.api.authorize(this.token);
try {
const { authorize } = await this.api.expectResponse('authorize');
- if (this.has_activeSymbols) {
+ if (this.has_active_symbols) {
this.toggleRunButton(false);
} else {
- this.getActiveSymbols();
+ this.active_symbols_promise = this.getActiveSymbols();
}
await this.subscribe();
this.account_info = authorize;
@@ -101,16 +104,19 @@ class APIBase {
}
getActiveSymbols = async () => {
- doUntilDone(() => this.api.send({ active_symbols: 'brief' })).then(({ active_symbols = [] }) => {
- const pip_sizes = {};
- if (active_symbols.length) this.has_activeSymbols = true;
- active_symbols.forEach(({ symbol, pip }) => {
- pip_sizes[symbol] = +(+pip).toExponential().substring(3);
- });
- this.pip_sizes = pip_sizes;
- this.toggleRunButton(false);
- this.active_symbols = active_symbols;
- });
+ await doUntilDone(() => this.api.send({ active_symbols: 'brief' })).then(
+ ({ active_symbols = [], error = {} }) => {
+ const pip_sizes = {};
+ if (active_symbols.length) this.has_active_symbols = true;
+ active_symbols.forEach(({ symbol, pip }) => {
+ pip_sizes[symbol] = +(+pip).toExponential().substring(3);
+ });
+ this.pip_sizes = pip_sizes;
+ this.toggleRunButton(false);
+ this.active_symbols = active_symbols;
+ return active_symbols || error;
+ }
+ );
};
toggleRunButton = toggle => {
diff --git a/src/external/bot-skeleton/services/api/api-helpers.js b/src/external/bot-skeleton/services/api/api-helpers.js
index de5f93e4..12ccc622 100644
--- a/src/external/bot-skeleton/services/api/api-helpers.js
+++ b/src/external/bot-skeleton/services/api/api-helpers.js
@@ -9,7 +9,7 @@ class ApiHelpers {
constructor(api_helpers_store) {
this.trading_times = new TradingTimes(api_helpers_store);
this.contracts_for = new ContractsFor(api_helpers_store);
- this.active_symbols = new ActiveSymbols(api_helpers_store.ws, this.trading_times);
+ this.active_symbols = new ActiveSymbols(this.trading_times);
this.account_limits = new AccountLimits(api_helpers_store);
}
diff --git a/src/external/bot-skeleton/services/api/chart-api.js b/src/external/bot-skeleton/services/api/chart-api.js
new file mode 100644
index 00000000..15aadc79
--- /dev/null
+++ b/src/external/bot-skeleton/services/api/chart-api.js
@@ -0,0 +1,25 @@
+import { generateDerivApiInstance, getLoginId, getToken } from './appId';
+
+class ChartAPI {
+ api;
+
+ init = async () => {
+ this.api = await generateDerivApiInstance();
+ if (getLoginId()) {
+ await this.api.authorize(getToken().token);
+ }
+ this.getTime();
+ };
+
+ getTime() {
+ if (!this.time_interval) {
+ this.time_interval = setInterval(() => {
+ this.api.send({ time: 1 });
+ }, 30000);
+ }
+ }
+}
+
+const chart_api = new ChartAPI();
+
+export default chart_api;
diff --git a/src/external/bot-skeleton/services/api/contracts-for.js b/src/external/bot-skeleton/services/api/contracts-for.js
index 21ab7c82..b4facbdb 100644
--- a/src/external/bot-skeleton/services/api/contracts-for.js
+++ b/src/external/bot-skeleton/services/api/contracts-for.js
@@ -1,6 +1,8 @@
import { config } from '../../constants/config';
import PendingPromise from '../../utils/pending-promise';
+import { api_base } from './api-base';
+
export default class ContractsFor {
constructor({ ws, server_time }) {
this.cache_age_in_min = 10;
@@ -196,7 +198,7 @@ export default class ContractsFor {
}
this.retrieving_contracts_for[symbol] = new PendingPromise();
- const response = await this.ws.send({ contracts_for: symbol });
+ const response = await api_base.api.send({ contracts_for: symbol });
if (response.error) {
return [];
diff --git a/src/main.tsx b/src/main.tsx
index 6300a444..cd67dfc8 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,5 @@
-import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app/App.tsx';
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-);
+ReactDOM.createRoot(document.getElementById('root')!).render();
diff --git a/src/pages/chart/chart.tsx b/src/pages/chart/chart.tsx
index b80c7c62..218c957d 100644
--- a/src/pages/chart/chart.tsx
+++ b/src/pages/chart/chart.tsx
@@ -2,13 +2,37 @@ import { useEffect } from 'react';
import classNames from 'classnames';
import { observer } from 'mobx-react-lite';
-import { ChartTitle, setSmartChartsPublicPath, SmartChart } from '@deriv/deriv-charts';
+import {
+ ActiveSymbolsRequest,
+ ServerTimeRequest,
+ TicksHistoryResponse,
+ TicksStreamRequest,
+ TradingTimesRequest,
+} from '@deriv/api-types';
+import { ChartTitle, SmartChart } from '@deriv/deriv-charts';
-import { getUrlBase } from '@/components/shared';
+import chart_api from '@/external/bot-skeleton/services/api/chart-api';
import { useStore } from '@/hooks/useStore';
import ToolbarWidgets from './toolbar-widgets';
+import '@deriv/deriv-charts/dist/smartcharts.css';
+
+type TSubscription = {
+ [key: string]: null | {
+ unsubscribe?: () => void;
+ };
+};
+
+type TError = null | {
+ error?: {
+ code?: string;
+ message?: string;
+ };
+};
+
+const subscriptions: TSubscription = {};
+
const Chart = observer(({ show_digits_stats }: { show_digits_stats: boolean }) => {
const barriers: [] = [];
const { common, ui } = useStore();
@@ -23,17 +47,14 @@ const Chart = observer(({ show_digits_stats }: { show_digits_stats: boolean }) =
symbol,
updateChartType,
updateGranularity,
- wsForget,
- wsForgetStream,
- wsSendRequest,
- wsSubscribe,
+ updateSymbol,
} = chart_store;
const {
ui: { is_mobile, is_desktop },
} = useStore();
const { is_drawer_open } = run_panel;
const { is_chart_modal_visible } = dashboard;
- const is_socket_opened = common.is_socket_opened;
+
const settings = {
assetInformation: false, // ui.is_chart_asset_info_visible,
countdown: true,
@@ -44,9 +65,51 @@ const Chart = observer(({ show_digits_stats }: { show_digits_stats: boolean }) =
};
useEffect(() => {
- setSmartChartsPublicPath(getUrlBase('/js/smartcharts/'));
+ return () => {
+ chart_api.api.forgetAll('ticks');
+ Object.keys(subscriptions).forEach(subscription_id => {
+ requestForgetStream(subscription_id);
+ });
+ };
}, []);
+ useEffect(() => {
+ if (!symbol) updateSymbol();
+ }, [symbol, updateSymbol]);
+
+ const requestAPI = (req: ServerTimeRequest | ActiveSymbolsRequest | TradingTimesRequest) => {
+ return chart_api.api.send(req);
+ };
+
+ const requestForget = () => {
+ return chart_api.api.forgetAll('ticks');
+ };
+
+ const requestForgetStream = (subscription_id: string) => {
+ subscriptions[subscription_id as string]?.unsubscribe();
+ delete subscriptions[subscription_id];
+ };
+
+ const requestSubscribe = async (req: TicksStreamRequest, callback: (data: any) => void) => {
+ try {
+ const history = await chart_api.api.send(req);
+ if (history) callback(history);
+ if (req.subscribe === 1) {
+ subscriptions[history?.subscription.id] = chart_api.api
+ .onMessage()
+ ?.subscribe(({ data }: { data: TicksHistoryResponse }) => {
+ callback(data);
+ });
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.log((e as TError)?.error?.message);
+ }
+ };
+
+ if (!symbol) return null;
+ const is_connection_opened = !!chart_api?.api;
+
return (
)}
chartType={chart_type}
isMobile={is_mobile}
enabledNavigationWidget={is_desktop}
granularity={granularity}
- requestAPI={wsSendRequest}
- requestForget={wsForget}
- requestForgetStream={wsForgetStream}
- requestSubscribe={wsSubscribe}
+ requestAPI={requestAPI}
+ requestForget={requestForget}
+ requestForgetStream={requestForgetStream}
+ requestSubscribe={requestSubscribe}
settings={settings}
symbol={symbol}
topWidgets={() => }
- isConnectionOpened={is_socket_opened}
+ isConnectionOpened={is_connection_opened}
getMarketsOrder={getMarketsOrder}
isLive
leftMargin={80}
diff --git a/src/pages/chart/toolbar-widgets.tsx b/src/pages/chart/toolbar-widgets.tsx
index 97c85a69..de8252b6 100644
--- a/src/pages/chart/toolbar-widgets.tsx
+++ b/src/pages/chart/toolbar-widgets.tsx
@@ -1,3 +1,5 @@
+import { memo } from 'react';
+
import { ChartMode, DrawTools, Share, StudyLegend, ToolbarWidget, Views } from '@deriv/deriv-charts';
import { isDesktop } from '@/components/shared';
@@ -10,23 +12,30 @@ type TToolbarWidgetsProps = {
const ToolbarWidgets = ({ updateChartType, updateGranularity, position }: TToolbarWidgetsProps) => {
return (
-
-
- {isDesktop() && (
- <>
-
-
-
-
- >
- )}
-
+ <>
+
+
+
+ {isDesktop() && (
+ <>
+
+
+
+
+ >
+ )}
+
+ >
);
};
-export default ToolbarWidgets;
+export default memo(ToolbarWidgets);
diff --git a/src/stores/chart-store.ts b/src/stores/chart-store.ts
index 1b918864..ac2d7b46 100644
--- a/src/stores/chart-store.ts
+++ b/src/stores/chart-store.ts
@@ -1,20 +1,13 @@
import { action, computed, makeObservable, observable, reaction } from 'mobx';
-import {
- ActiveSymbolsRequest,
- ServerTimeRequest,
- TicksHistoryRequest,
- TicksStreamRequest,
- TradingTimesRequest,
-} from '@deriv/api-types';
-
import { LocalStore } from '@/components/shared';
-import { ServerTime } from '@/external/bot-skeleton';
import RootStore from './root-store';
-export const g_subscribers_map: Partial>> = {};
-let WS: RootStore['ws'];
+type TSubscription = {
+ id: string | null;
+ subscriber: null | { unsubscribe: () => void };
+};
export default class ChartStore {
root_store: RootStore;
@@ -34,7 +27,6 @@ export default class ChartStore {
});
this.root_store = root_store;
- WS = root_store.ws;
const { run_panel } = root_store;
reaction(
@@ -45,6 +37,11 @@ export default class ChartStore {
this.restoreFromStorage();
}
+ subscription: TSubscription = {
+ id: null,
+ subscriber: null,
+ };
+
symbol: string | undefined;
is_chart_loading: boolean | undefined;
chart_type: string | undefined;
@@ -126,42 +123,6 @@ export default class ChartStore {
}
};
- // #region WS
- wsSubscribe = (req: TicksStreamRequest, callback: () => void) => {
- if (req.subscribe === 1) {
- const key = JSON.stringify(req);
- const subscriber = WS?.subscribeTicksHistory(req, callback);
- g_subscribers_map[key] = subscriber;
- }
- };
-
- wsForget = (req: TicksHistoryRequest) => {
- const key = JSON.stringify(req);
- if (g_subscribers_map[key]) {
- g_subscribers_map[key]?.unsubscribe();
- delete g_subscribers_map[key];
- }
- };
-
- wsForgetStream = (stream_id: string) => {
- WS?.forgetStream(stream_id);
- };
-
- wsSendRequest = (req: TradingTimesRequest | ActiveSymbolsRequest | ServerTimeRequest) => {
- if ('time' in req && req.time) {
- return ServerTime.timePromise().then(() => {
- return {
- msg_type: 'time',
- time: ServerTime.get().unix(),
- };
- });
- }
- if ('active_symbols' in req && req.active_symbols) {
- return WS?.activeSymbols();
- }
- if (WS?.storage.send) return WS?.storage.send(req);
- };
-
getMarketsOrder = (active_symbols: { market: string; display_name: string }[]) => {
const synthetic_index = 'synthetic_index';
diff --git a/src/stores/load-modal-store.ts b/src/stores/load-modal-store.ts
index f0e5fa90..4c78f3fa 100644
--- a/src/stores/load-modal-store.ts
+++ b/src/stores/load-modal-store.ts
@@ -163,7 +163,7 @@ export default class LoadModalStore implements ILoadModalStore {
get selected_strategy(): TStrategy {
return (
- this.dashboard_strategies.find((ws: { id: string }) => ws.id === this.selected_strategy_id) ??
+ this.dashboard_strategies.find((workspace: { id: string }) => workspace.id === this.selected_strategy_id) ??
this.dashboard_strategies[0]
);
}
diff --git a/src/stores/root-store.ts b/src/stores/root-store.ts
index 34c4a8e7..92b85643 100644
--- a/src/stores/root-store.ts
+++ b/src/stores/root-store.ts
@@ -43,7 +43,6 @@ export default class RootStore {
public data_collection_store: DataCollectionStore;
core = {};
- ws = null;
constructor(dbot: unknown) {
this.dbot = dbot;
diff --git a/src/stores/self-exclusion-store.ts b/src/stores/self-exclusion-store.ts
index c186d696..8496e23b 100644
--- a/src/stores/self-exclusion-store.ts
+++ b/src/stores/self-exclusion-store.ts
@@ -67,8 +67,8 @@ export default class SelfExclusionStore {
async checkRestriction() {
if (api_base.api) {
- api_base.api.getSelfExclusion().then(response => {
- const { max_losses: maxLosses } = response.get_self_exclusion;
+ api_base.api.getSelfExclusion().then(({ get_self_exclusion }) => {
+ const { max_losses: maxLosses } = get_self_exclusion;
if (maxLosses) {
this.setApiMaxLosses(maxLosses);
}
diff --git a/src/stores/summary-card-store.ts b/src/stores/summary-card-store.ts
index f8a7d390..0aa93a31 100644
--- a/src/stores/summary-card-store.ts
+++ b/src/stores/summary-card-store.ts
@@ -7,6 +7,7 @@ import { getIndicativePrice, isEqualObject, isMultiplierContract } from '@/compo
import { TContractInfo } from '@/components/summary/summary-card.types';
import { getValidationRules, TValidationRuleIndex, TValidationRules } from '@/constants/contract';
import { contract_stages } from '@/constants/contract-stage';
+import { api_base } from '@/external/bot-skeleton';
import { getContractUpdateConfig } from '@/utils/multiplier';
import Validator from '@/utils/tmp/validator';
@@ -212,7 +213,7 @@ export default class SummaryCardStore {
const limit_order = this.getLimitOrder();
if (this.contract_info?.contract_id) {
- this.root_store.ws.contractUpdate(this.contract_info?.contract_id, limit_order).then(response => {
+ api_base.api.contractUpdate(this.contract_info?.contract_id, limit_order).then(response => {
if (response.error) {
this.root_store.run_panel.showContractUpdateErrorDialog(response.error.message);
return;
diff --git a/vite.config.ts b/vite.config.ts
index 254e961e..0ecd3297 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,4 +1,5 @@
import { defineConfig } from 'vite';
+import { viteStaticCopy } from 'vite-plugin-static-copy';
import svgr from 'vite-plugin-svgr';
import { viteRequire } from 'vite-require';
import tsconfigPaths from 'vite-tsconfig-paths';
@@ -28,6 +29,18 @@ export default defineConfig({
}
},
},
+ viteStaticCopy({
+ targets: [
+ {
+ src: 'node_modules/@deriv/deriv-charts/dist/*',
+ dest: 'js/smartcharts',
+ },
+ {
+ src: 'node_modules/@deriv/deriv-charts/dist/chart/assets/*',
+ dest: 'assets',
+ },
+ ],
+ }),
],
css: {
preprocessorOptions: {