From fe0381cb6357b801985004b89d5198ab53f95e2b Mon Sep 17 00:00:00 2001 From: LiZhenhai-MBP14 <5935568+jackhai9@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:59:13 +0800 Subject: [PATCH] fix: scope native order reminders to the current futures symbol --- docs/binance-chart-storage-development.md | 7 + docs/binance-order-notifications.md | 95 ++ docs/binance-orderbook-trade-development.md | 10 + .../helpers/order-notifications-host.js | 192 ++++ .../specs/order-notifications.pw.js | 87 ++ package-lock.json | 61 +- package.json | 4 +- scripts/binance-orderbook-trade.user.js | 521 +++++++++- .../generate-order-notification-modules.mjs | 248 +++++ .../chart-storage/install.js | 7 +- .../chart-storage/runtime.js | 25 +- src/binance-orderbook-trade/index.user.js | 2 +- .../order-notifications/native-modules.js | 52 + .../order-notifications/runtime.js | 67 ++ .../order-notifications/scope.js | 39 + .../order-notifications.test.js | 486 +++++++++ .../native-factories.json | 270 +++++ .../notification-manifest.json | 955 ++++++++++++++++++ .../original-factories.js | 149 +++ test/helpers/binance-order-notifications.js | 344 +++++++ .../binance-chart-storage-runtime.test.js | 108 +- 21 files changed, 3718 insertions(+), 11 deletions(-) create mode 100644 docs/binance-order-notifications.md create mode 100644 e2e/binance-orderbook/helpers/order-notifications-host.js create mode 100644 e2e/binance-orderbook/specs/order-notifications.pw.js create mode 100644 scripts/generate-order-notification-modules.mjs create mode 100644 src/binance-orderbook-trade/order-notifications/native-modules.js create mode 100644 src/binance-orderbook-trade/order-notifications/runtime.js create mode 100644 src/binance-orderbook-trade/order-notifications/scope.js create mode 100644 test/dom/binance-orderbook-trade/order-notifications.test.js create mode 100644 test/fixtures/binance-order-notifications/native-factories.json create mode 100644 test/fixtures/binance-order-notifications/notification-manifest.json create mode 100644 test/fixtures/binance-order-notifications/original-factories.js create mode 100644 test/helpers/binance-order-notifications.js diff --git a/docs/binance-chart-storage-development.md b/docs/binance-chart-storage-development.md index 1b3f5af..e559e78 100644 --- a/docs/binance-chart-storage-development.md +++ b/docs/binance-chart-storage-development.md @@ -25,6 +25,13 @@ outcomes are independent; one changed upstream module does not cancel a matching other module. Late injection leaves both native. Metadata does not guarantee early injection on every extension configuration; rejection remains explicit. +The orderbook installer also supplies independently tracked +[notification targets](binance-order-notifications.md) to this same observer. +`startChartStorageOptimizer({ additionalTargets })` accepts explicit +`replace`, `onCapture` and `onFailure` callbacks for each non-storage module ID; +storage IDs cannot be replaced by those descriptors. They share the original +capture deadline, but mirror `stop()` does not stop their capture or handling. + ### Drawing ownership Native loading puts historical symbols' drawings into every chart that shares diff --git a/docs/binance-order-notifications.md b/docs/binance-order-notifications.md new file mode 100644 index 0000000..d7a5d6f --- /dev/null +++ b/docs/binance-order-notifications.md @@ -0,0 +1,95 @@ +# Native Order Notification Scope + +The orderbook userscript scopes Binance's ordinary order toasts and trade sound +to the symbol currently displayed by its supported USDT/USDC futures route. It +does not filter the account stream, positions, open orders, or native trade +acknowledgements. There is no separate userscript or notification preference. +The native toast and sound switches retain their meaning. + +## Observed cause and intervention + +The public factories captured on 2026-10-07 show two independent paths: + +- `39116` subscribes to both UM and CM account order streams. FILLED, + PARTIALLY_FILLED and EXPIRED events enter a 500 ms trailing debounce with only + the order ID. There is no symbol test. `55401` plays queued notifications with + a two-second cooldown; its audio helper calls `play()` twice. +- `4189` updates the native account cache and emits order events using the old + cached order. `34122` registers notification listeners. `30877` converts + cancellation, partial-fill and fill events into toasts through a 30 ms + debounce, without checking the page symbol. + +`order-notifications/native-modules.js` pins complete source for `39116`, `55401` +and all six observed `30877` variants. Changes are limited to those notification +consumers. Ordinary events are checked before debounce and immediately before +presentation. Queued tokens contain only a symbol, or null for native handling; +order IDs, client order IDs, prices and quantities are not retained in tokens. +Both audio calls check the same selected token. The sound queue is pruned only +when native playback can start, never while its current cooldown still owns the +queue head. + +The page symbol is read on each check, including after a debounce or pending +audio Promise. Each matching tab may notify, even if two tabs show the same +symbol. A symbol with no matching tab produces no ordinary notification in the +other supported trading tabs. Unknown routes retain native handling. + +## Risk and unknown events + +Only known ordinary order types with a valid exchange symbol and nonempty +`clientOrderId` are scoped. `origType` and `operate`, when present, must also be +known ordinary values. The native REST cache can omit those stream fields, so +their absence alone does not classify a toast as unknown. Notifications receive +the cached record, not necessarily the latest stream execution type. + +LIQUIDATION, CALCULATED, unknown shapes and the documented system client ID +prefixes `autoclose-`, `adl_autoclose` and `settlement_autoclose-` retain native +handling. This does not add a toast that Binance itself suppresses. Other +account/system alerts do not enter the patched consumers. The public `5558` +adapter preserves the corresponding raw `s`, `o`, `c`, `x` and `ot` fields. +See Binance's [system order ID documentation](https://www.binance.com/zh-CN/support/announcement/detail/f2809259702b46f7abdc9c97b977908f) +and [order stream reference](https://developers.binance.com/en/docs/products/derivatives-trading-coin-futures/user-data-streams). + +## Startup and source changes + +The existing orderbook installer supplies notification target descriptors to the +single early chart-storage queue observer. Each target has independent capture +and failure outcomes; stopping mirror writes does not stop notification scope. +No second queue accessor, global Audio override, WebSocket replacement, new +account subscription or DOM notification removal is installed by this feature. + +Both sound factories must execute their pinned replacements before either +changes the native string queue to classified tokens. If either source mismatches, +both sides retain the original string contract. Pending native strings are also +preserved during activation. Toast capture is independent. Late injection, +unknown source or the original 30-second capture deadline leaves the affected +path native and reports its reason; native reminders may therefore still repeat +after an upstream change. + +`self.__BINANCE_ORDER_NOTIFICATIONS__.snapshot()` exposes only capture status and +aggregate scope-check counts. `suppressedChecks` is a check count, not a unique +order or notification count. No account data is exposed by the diagnostic API. +Already-open pages require a fresh script load to activate this early hook. + +## Reproduction and validation + +`test/fixtures/binance-order-notifications/notification-manifest.json` records +factory hashes, observed source URLs, available complete chunk hashes and every +AST replacement. Three toast variants were captured as complete function source +without the complete chunk response; that distinction is explicit in the +manifest. Generate with: + +```sh +node scripts/generate-order-notification-modules.mjs +npm run build:binance-orderbook-trade +node --test test/dom/binance-orderbook-trade/order-notifications.test.js test/unit/binance-chart-storage-runtime.test.js +npm run lint:tests +npm run check:binance-orderbook-trade +``` + +The regression first failed against the complete unmodified factories: an ETH +fill on a BTC page produced the native toast and load/play/play calls. Isolated +tests run the captured adapters, event cache, listeners, React 18.2 hooks and +native debounce with controlled clocks. Only preferences, account stream and +presentation I/O are replaced. Real orders are never required to validate this +feature. Installed-source and live notification behavior remain separate from +these deterministic checks. diff --git a/docs/binance-orderbook-trade-development.md b/docs/binance-orderbook-trade-development.md index 7021fb1..a0e6ad3 100644 --- a/docs/binance-orderbook-trade-development.md +++ b/docs/binance-orderbook-trade-development.md @@ -20,6 +20,16 @@ scripts/binance-orderbook-trade.user.js ## Runtime +### Native order reminders + +`order-notifications/` scopes native ordinary order toasts and sound to the +current futures symbol. The storage installer shares its single early queue +observer with these independently pinned targets; no separate userscript is +installed. Risk and unknown events retain native handling. The native account +data flow and trading feedback readers remain unchanged. See +[notification scope](binance-order-notifications.md) for source pins, delayed +delivery, cross-tab behavior and validation boundaries. + ### Chart mirror storage `chart-storage/install.js` runs before the orderbook business initializer. This diff --git a/e2e/binance-orderbook/helpers/order-notifications-host.js b/e2e/binance-orderbook/helpers/order-notifications-host.js new file mode 100644 index 0000000..885ee1d --- /dev/null +++ b/e2e/binance-orderbook/helpers/order-notifications-host.js @@ -0,0 +1,192 @@ +import { readFile } from 'node:fs/promises'; +import { build } from 'esbuild'; + +import { nativeOrderNotificationSources } from '../../../test/fixtures/binance-order-notifications/original-factories.js'; +import { openUserscriptScenario } from './userscript-page.js'; +import { createCancelScenario } from '../scenarios/cancel-current-symbol.js'; + +let dependenciesSource; + +/** Bundle only library dependencies; captured native factories retain their exact source. */ +async function readReactDependencies() { + if (!dependenciesSource) { + const result = await build({ + stdin: { + contents: `import * as React from 'react'; import {createRoot} from 'react-dom/client'; import {flushSync} from 'react-dom'; import {jsx,jsxs} from 'react/jsx-runtime'; self.__NOTIFICATION_REACT__={React,createRoot,flushSync,jsx,jsxs};`, + resolveDir: new URL('../../../', import.meta.url).pathname, + }, + bundle: true, + write: false, + platform: 'browser', + format: 'iife', + define: { 'process.env.NODE_ENV': '"production"' }, + }); + dependenciesSource = result.outputFiles[0].text; + } + return dependenciesSource; +} + +/** Native code owns all classification, events, throttles and playback scheduling. */ +function installNotificationHost(nativeFactories) { + const { React, createRoot, flushSync, jsx, jsxs } = self.__NOTIFICATION_REACT__; + const channels = new Map([[false, new Set()], [true, new Set()]]); + const audio = []; + const toasts = []; + const parameters = Object.freeze({}); + const getSDK = ({ isCM }) => ({ + getUserOrderStream: () => ({ + subscribe(listener) { + channels.get(isCM).add(listener); + return () => channels.get(isCM).delete(listener); + }, + }), + }); + const emit = (order, isCM) => { + for (const listener of channels.get(isCM)) listener(order); + }; + const createExternalStore = (initialize) => { + const listeners = new Set(); + let state = initialize((change) => { + state = { ...state, ...change }; + for (const listener of listeners) listener(); + }); + const subscribe = (listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }; + const snapshot = () => state; + return () => React.useSyncExternalStore(subscribe, snapshot, snapshot); + }; + HTMLMediaElement.prototype.load = function load() { + audio.push({ kind: 'load', pathname: location.pathname }); + }; + HTMLMediaElement.prototype.play = function play() { + audio.push({ kind: 'play', pathname: location.pathname }); + return Promise.resolve(); + }; + const enqueueNotification = (message, options) => { + toasts.push({ message, options, pathname: location.pathname }); + const alert = document.createElement('div'); + alert.setAttribute('role', 'alert'); + alert.textContent = message; + alert.style.cssText = 'padding:16px 24px;margin:8px;background:#102a20;color:#d7ffec;border:1px solid #43b581;border-radius:8px;font:16px system-ui;box-shadow:0 5px 24px #0004'; + document.getElementById('native-order-notifications').appendChild(alert); + }; + const stores = new Map(); + const queryClient = { setQueryData: (key, update) => stores.set(key, update(stores.get(key))) }; + const makeBatcher = () => { + const workers = new Set(); + return { + addWorker: (worker) => workers.add(worker), + removeWorker: (worker) => workers.delete(worker), + push: (order) => { + for (const worker of workers) worker(Array.isArray(order) ? order : [order]); + }, + }; + }; + const cmBatcher = makeBatcher(); + const umBatcher = makeBatcher(); + const dependencies = { + 41594: React, + 31085: { jsx, jsxs }, + 75510: { _: (array) => [...array] }, + 94917: { _: (instance, Constructor) => { + if (!(instance instanceof Constructor)) throw new TypeError('Expected native constructor invocation'); + } }, + 17409: { a0: 'classic', K5: 'https://static.test.invalid' }, + 61523: { d4: (selector) => selector({ setting: { layout: 'classic' } }) }, + 92873: { o: () => ({ getI18n: (_key, options) => options.defaultValue }) }, + 64041: { h: () => ({ enqueueNotification }) }, + 51471: { zr: (key) => { + if (!['open_order_status_toast', 'open-order-notification-sound-open'].includes(key)) throw new Error('Unknown native preference'); + const [data, setData] = React.useState(true); + return { data, setData, hasInitialized: true }; + } }, + 80065: { A: createExternalStore }, + 96636: { Z: () => ({ isEUFuturesUrl: false }) }, + 16921: { Gw: () => parameters }, + 72363: { Ri: getSDK }, + 79515: { nH: () => true, Py: () => ({ isExistFutureAccount: true }), ON: () => ({ isPM2: false }) }, + 88478: { A: () => null }, + 48651: { ud: () => ({ getI18n: (_key, options) => options.defaultValue }) }, + 34175: { Zu: () => undefined }, + 10157: { XE: 'notification-switch', IG: 'notification-label' }, + 87017: {}, + 48187: { mp: () => { throw new Error('Notification fixture cannot access financial APIs'); } }, + 57861: { post: () => { throw new Error('Notification fixture cannot access financial APIs'); } }, + 41466: {}, + 43335: { Bz: { OPEN_ORDERS: (isCM) => `orders:${isCM}` } }, + 26860: {}, + 84266: {}, + 90291: {}, + 47738: { Y: ({ subscribeToStreamFn }) => (options) => subscribeToStreamFn({ + ...options, stream: options.getSDK({ isCM: options.isCM }), + }) }, + 95541: { CX: cmBatcher, kc: () => umBatcher }, + }; + const hostFactories = Object.fromEntries(Object.entries(dependencies).map(([id, value]) => [id, (module) => { module.exports = value; }])); + self.webpackChunkfutures_trade_ui.push([['notification-host'], { ...hostFactories, ...nativeFactories }, (require) => { + const NativeEmitter = require(22584).b; + const emitter = new NativeEmitter(); + require.m[71822] = (module) => { module.exports = { J: emitter }; }; + const nativeOrders = require(4189); + for (const isCM of [false, true]) { + nativeOrders.$t({ enabled: true, isCM, isPM2: false, getSDK, queryClient, copyTradingPayload: parameters }); + } + const Provider = require(55401).SoundNotificationProvider; + const Register = require(34122).OrderToastNotifyRegister; + const mount = () => { + const presentation = document.createElement('section'); + presentation.id = 'native-order-notifications'; + presentation.setAttribute('aria-label', 'Native order reminders'); + presentation.style.cssText = 'position:fixed;top:24px;right:24px;z-index:2147483646;max-width:410px'; + const rootElement = document.createElement('div'); + rootElement.id = 'native-order-notification-provider'; + document.body.append(presentation, rootElement); + const root = createRoot(rootElement); + flushSync(() => root.render(React.createElement(Provider, null, React.createElement(Register)))); + self.__NOTIFICATION_HOST__ = { + ready: () => channels.get(false).size === 2 && channels.get(true).size === 2, + notify(symbol, orderId) { + const order = { symbol, orderId, clientOrderId: `manual-${orderId}`, type: 'LIMIT', orderType: 'LIMIT', origType: 'LIMIT', side: 'BUY', operate: 'TRADE', status: 'FILLED' }; + flushSync(() => { + emit({ ...order, status: 'NEW' }, false); + emit(order, false); + }); + }, + snapshot() { + flushSync(() => undefined); + return { audio: [...audio], toasts: [...toasts] }; + }, + }; + }; + if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount, { once: true }); + else mount(); + }]); +} + +/** Install the built artifact at document-start, then enter through the real Rspack queue. */ +export async function openNotificationScenario(page, symbol) { + await page.route('**/*', (route) => route.abort('blockedbyclient')); + const [artifact, runtime, react] = await Promise.all([ + readFile(new URL('../../../scripts/binance-orderbook-trade.user.js', import.meta.url), 'utf8'), + readFile(new URL('../../../test/fixtures/binance-chart-storage/webpack-runtime.js', import.meta.url), 'utf8'), + readReactDependencies(), + ]); + await page.addInitScript({ content: `self.__NOTIFICATION_EARLY_ROOT__=Boolean(document.documentElement);\n${artifact}` }); + const ids = new Set(['30877', '39116', '55401', '40477', '70020', '22584', '34122', '4189']); + const factories = nativeOrderNotificationSources.filter((entry) => ids.has(entry.id) + && (entry.id !== '30877' || entry.chunks.includes('37511'))).map((entry) => entry.source).join(',\n'); + const host = await openUserscriptScenario(page, createCancelScenario({ currentSymbol: symbol }), { + beforeOrderbook: `${react}\n${runtime}\n(${installNotificationHost.toString()})({${factories}});\nif(false){`, + afterOrderbook: '}', + }); + await page.waitForFunction(() => self.__NOTIFICATION_HOST__?.ready() === true); + await page.clock.install({ time: new Date('2026-10-07T12:00:00Z') }); + await page.clock.pauseAt(new Date('2026-10-07T12:00:01Z')); + return host; +} + +export async function readNotificationEffects(page) { + return page.evaluate(() => self.__NOTIFICATION_HOST__.snapshot()); +} diff --git a/e2e/binance-orderbook/specs/order-notifications.pw.js b/e2e/binance-orderbook/specs/order-notifications.pw.js new file mode 100644 index 0000000..885bc79 --- /dev/null +++ b/e2e/binance-orderbook/specs/order-notifications.pw.js @@ -0,0 +1,87 @@ +import { test, expect } from '../test.js'; +import { openNotificationScenario, readNotificationEffects } from '../helpers/order-notifications-host.js'; +import { readFixtureState } from '../helpers/userscript-page.js'; + +const panel = '#jh-binance-close-qty-multiplier-panel'; + +test('user sees and hears each ordinary fill only in the matching isolated futures tab', async ({ page, browser }, testInfo) => { + // Given two isolated Chromium pages load the real generated userscript before their native runtime + const otherContext = await browser.newContext({ viewport: { width: 1440, height: 1000 } }); + const ethPage = await otherContext.newPage(); + try { + const btcHost = await openNotificationScenario(page, 'BTCUSDT'); + const ethHost = await openNotificationScenario(ethPage, 'ETHUSDT'); + + // When both tabs receive the same synthetic BTC account fill through real native hooks + await page.evaluate(() => self.__NOTIFICATION_HOST__.notify('BTCUSDT', 42)); + await ethPage.evaluate(() => self.__NOTIFICATION_HOST__.notify('BTCUSDT', 42)); + await page.clock.runFor(500); + await ethPage.clock.runFor(500); + + // Then only BTC presents the original native toast and audio while both orderbooks remain usable + await expect(page.getByRole('alert')).toHaveText('LIMIT BUY Order Filled'); + await expect(ethPage.getByRole('alert')).toHaveCount(0); + expect((await readNotificationEffects(page)).audio.map((event) => event.kind)).toEqual(['load', 'play', 'play']); + expect(await readNotificationEffects(ethPage)).toEqual({ audio: [], toasts: [] }); + await expect(page.locator(panel)).toBeVisible(); + await expect(ethPage.locator(panel)).toBeVisible(); + expect(await page.evaluate(() => self.__BINANCE_ORDER_NOTIFICATIONS__.snapshot())).toMatchObject({ toastActive: true, soundActive: true }); + expect(await ethPage.evaluate(() => self.__BINANCE_ORDER_NOTIFICATIONS__.snapshot())).toMatchObject({ toastActive: true, soundActive: true }); + expect(await page.evaluate(() => self.__NOTIFICATION_EARLY_ROOT__)).toBe(false); + + // When the same independent pages receive an ETH fill after the original native cooldown + await page.clock.runFor(2000); + await ethPage.clock.runFor(2000); + await page.evaluate(() => self.__NOTIFICATION_HOST__.notify('ETHUSDT', 43)); + await ethPage.evaluate(() => self.__NOTIFICATION_HOST__.notify('ETHUSDT', 43)); + await page.clock.runFor(500); + await ethPage.clock.runFor(500); + + // Then ETH gets exactly its own reminder and the BTC tab gains no extra presentation + await expect(page.getByRole('alert')).toHaveCount(1); + await expect(ethPage.getByRole('alert')).toHaveText('LIMIT BUY Order Filled'); + expect((await readNotificationEffects(page)).audio.map((event) => event.kind)).toEqual(['load', 'play', 'play']); + expect((await readNotificationEffects(ethPage)).audio.map((event) => event.kind)).toEqual(['load', 'play', 'play']); + expect(btcHost.errors).toEqual([]); + expect(ethHost.errors).toEqual([]); + expect((await readFixtureState(page)).events.filter((event) => ['order-submitted', 'cancel-requested'].includes(event.type))).toEqual([]); + expect((await readFixtureState(ethPage)).events.filter((event) => ['order-submitted', 'cancel-requested'].includes(event.type))).toEqual([]); + await page.screenshot({ path: testInfo.outputPath('btc-native-reminder.png'), fullPage: true }); + await ethPage.screenshot({ path: testInfo.outputPath('eth-native-reminder.png'), fullPage: true }); + } finally { + await otherContext.close(); + } +}); + +test('user receives only the new-symbol reminder after SPA navigation during native delays', async ({ page }, testInfo) => { + // Given BTC has queued an ordinary reminder inside the real native toast and audio delays + const host = await openNotificationScenario(page, 'BTCUSDT'); + await page.evaluate(() => self.__NOTIFICATION_HOST__.notify('BTCUSDT', 42)); + await page.clock.runFor(29); + + // When SPA navigation switches to ETH before either native notification deadline + await page.evaluate(() => history.replaceState({}, '', '/zh-CN/futures/ETHUSDT')); + await page.clock.runFor(471); + + // Then the stale BTC reminder has no rendered or audible effect on the new route + await expect(page.getByRole('alert')).toHaveCount(0); + expect(await readNotificationEffects(page)).toEqual({ audio: [], toasts: [] }); + await expect(page.locator(panel)).toBeVisible(); + + // When an ETH order completes on the new route without reinjecting the userscript + await page.evaluate(() => self.__NOTIFICATION_HOST__.notify('ETHUSDT', 43)); + await page.clock.runFor(500); + + // Then ETH receives its original native reminder and the orderbook remains active without page errors + await expect(page.getByRole('alert')).toHaveText('LIMIT BUY Order Filled'); + expect((await readNotificationEffects(page)).audio).toEqual([ + { kind: 'load', pathname: '/zh-CN/futures/ETHUSDT' }, + { kind: 'play', pathname: '/zh-CN/futures/ETHUSDT' }, + { kind: 'play', pathname: '/zh-CN/futures/ETHUSDT' }, + ]); + expect(await page.evaluate(() => self.__BINANCE_ORDER_NOTIFICATIONS__.snapshot())).toMatchObject({ toastActive: true, soundActive: true }); + await expect(page.locator(panel)).toBeVisible(); + expect(host.errors).toEqual([]); + expect((await readFixtureState(page)).events.filter((event) => ['order-submitted', 'cancel-requested'].includes(event.type))).toEqual([]); + await page.screenshot({ path: testInfo.outputPath('spa-eth-native-reminder.png'), fullPage: true }); +}); diff --git a/package-lock.json b/package-lock.json index ddc0327..31d5983 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "esbuild": "^0.25.0", "eslint": "10.10.0", "jsdom": "^29.1.1", - "monocart-coverage-reports": "2.13.0" + "monocart-coverage-reports": "2.13.0", + "react": "18.2.0", + "react-dom": "18.2.0" } }, "node_modules/@asamuzakjp/css-color": { @@ -1644,6 +1646,13 @@ "node": ">=8" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jsdom": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", @@ -1739,6 +1748,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "11.5.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", @@ -1995,6 +2017,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -2018,6 +2067,16 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", diff --git a/package.json b/package.json index 919b31a..18d82c7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "esbuild": "^0.25.0", "eslint": "10.10.0", "jsdom": "^29.1.1", - "monocart-coverage-reports": "2.13.0" + "monocart-coverage-reports": "2.13.0", + "react": "18.2.0", + "react-dom": "18.2.0" } } diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js index f8eb350..dbca8dd 100644 --- a/scripts/binance-orderbook-trade.user.js +++ b/scripts/binance-orderbook-trade.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.219 +// @version 2.7.220 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* @@ -6767,8 +6767,13 @@ var CAPTURE_DEADLINE_MS = 3e4; var MIRROR_MODULE = "70940"; var DRAWING_MODULE = "76535"; - function startChartStorageOptimizer() { + function startChartStorageOptimizer({ additionalTargets = {} } = {}) { if (!isChartStoragePage()) throw new Error("Chart storage requires a top-level Binance trading page"); + for (const [id, target] of Object.entries(additionalTargets)) { + if (id === MIRROR_MODULE || id === DRAWING_MODULE || !target || [target.replace, target.onCapture, target.onFailure].some((callback) => typeof callback !== "function")) { + throw new Error("Additional native targets require independent replacement and outcome callbacks"); + } + } const writer = createChartMirrorWriter(); const state = { status: "waiting", reason: null, attempts: 0, matches: 0, executions: 0 }; const drawingScope = { status: "waiting", reason: null, attempts: 0, matches: 0, executions: 0 }; @@ -6830,13 +6835,29 @@ observer = observeChartStorageBootstrap({ targets: { [MIRROR_MODULE]: (original) => replaceFactory(MIRROR_MODULE, original, (factory) => replaceChartMirrorFactory(factory, dispatch)), - [DRAWING_MODULE]: (original) => replaceFactory(DRAWING_MODULE, original, replaceChartDrawingSaveFactory) + [DRAWING_MODULE]: (original) => replaceFactory(DRAWING_MODULE, original, replaceChartDrawingSaveFactory), + ...Object.fromEntries(Object.entries(additionalTargets).map(([id, target]) => [id, (original) => { + if (!isChartStoragePage()) { + observer.stop("scope_changed"); + void stop("scope_changed"); + return original; + } + return target.replace(original); + }])) }, onCapture(id) { + if (Object.hasOwn(additionalTargets, id)) { + additionalTargets[id].onCapture(); + return; + } const targetState = id === MIRROR_MODULE ? state : drawingScope; if (id !== MIRROR_MODULE || !stopping) targetState.status = "active"; }, onFailure(id, reason) { + if (Object.hasOwn(additionalTargets, id)) { + additionalTargets[id].onFailure(reason); + return; + } if (id === MIRROR_MODULE) { void stop(reason === "execution_failed" ? "capture_failed" : reason); } else { @@ -6849,6 +6870,7 @@ } }); } catch { + for (const target of Object.values(additionalTargets)) target.onFailure("bootstrap_unavailable"); drawingScope.status = "unavailable"; drawingScope.reason = "bootstrap_unavailable"; finishObservation(); @@ -6865,11 +6887,502 @@ return Object.freeze({ snapshot, stop }); } + // src/binance-orderbook-trade/order-notifications/native-modules.js + var pinnedFactories = [ + { + id: "39116", + original: '39116(D,l,t){"use strict";t.d(l,{QZ:()=>p.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(A,500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&u(String(P.orderId))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&u(String(P.orderId))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length) p.Q, $$: () => n, E$: () => S }); + var e = t(41594), a = 2e3, r = 2, n = function() { + var E = (0, e.useRef)(null), A = function(O) { + E.current = setTimeout(function() { + O(), E.current && clearTimeout(E.current), E.current = null; + }, a); + }; + return (0, e.useMemo)(function() { + return { interval: E, intervalFunc: A }; + }, []); + }, i = t(75510), s = t(16921), c = t(72363), d = t(79515), y = t(40477), p = t(70020), T = function(E, A) { + var O = (0, d.nH)(), m = (0, d.Py)().isExistFutureAccount, g = (0, y.A)(function(__event) { + if (__notify.allowSoundInput(__event)) return Reflect.apply(A, this, [__event]); + }, 500), u = g.run, o = (0, d.ON)().isPM2, f = (0, s.Gw)(); + (0, e.useEffect)(function() { + if (!O || !m || !E) return function() { + return null; + }; + var _ = (0, c.Ri)({ isCM: true }); + return _.getUserOrderStream({ isPM2: o, params: f }).subscribe(function(v) { + var L = Array.isArray(v) ? (0, i._)(v) : [v]; + L.forEach(function(P) { + (P.status === "FILLED" || P.status === "PARTIALLY_FILLED" || P.status === "EXPIRED") && ((!__notify.soundReady() || __notify.allowOrder(P)) && u(__notify.soundInput(P))); + }); + }); + }, [u, E, m, O, o, f]), (0, e.useEffect)(function() { + if (!O || !m || !E) return function() { + return null; + }; + var _ = (0, c.Ri)({ isCM: false }); + return _.getUserOrderStream({ isPM2: o, params: f }).subscribe(function(v) { + var L = Array.isArray(v) ? (0, i._)(v) : [v]; + L.forEach(function(P) { + (P.status === "FILLED" || P.status === "PARTIALLY_FILLED" || P.status === "EXPIRED") && ((!__notify.soundReady() || __notify.allowOrder(P)) && u(__notify.soundInput(P))); + }); + }); + }, [u, E, m, O, o, f]); + }, S = function() { + var E = (0, e.useState)(/* @__PURE__ */ new Date()), A = E[0], O = E[1], m = (0, e.useRef)([]), g = (0, p.Q)(), u = g.showNotification, o = g.enableNotification, f = (0, e.useCallback)(function() { + var v; + (v = m) === null || v === void 0 || v.current.shift(), O(/* @__PURE__ */ new Date()); + }, []), _ = (0, e.useCallback)(function(v) { + var L; + if (u && ((L = m) === null || L === void 0 ? void 0 : L.current.length) < r && o && v) { + var P; + (P = m) === null || P === void 0 || P.current.push(v), O(/* @__PURE__ */ new Date()); + } + }, [o, u]); + return T(u && o, _), (0, e.useMemo)(function() { + return { notifications: m, lastNotifyTime: A, popNotification: f }; + }, [A, f]); + }; + } }[39116]; + } + }, + { + id: "30877", + original: '30877(be,W,e){e.d(W,{F:()=>N,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$="trd-openOrder",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)("open_order_status_toast",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(h,30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,"");return"".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"CancelTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Canceled")});x(Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"FillTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Filled")});x(Y,{variant:"success"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"PFTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Partially Filled")});x(Y,{variant:"success"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}', + create(__notify) { + return { 30877(be, W, e) { + e.d(W, { F: () => N, z: () => o }); + var r = e(41594), a = e.n(r), T = e(61523), p = e(92873), P = e(64041), b = e(17409), S = e(51471), E = e(40477), $ = "trd-openOrder", G = function(c) { + return (0, p.o)($, c); + }, o = function() { + var c = (0, S.zr)("open_order_status_toast", true), h = c.data, R = c.setData, K = c.hasInitialized, x = (0, r.useCallback)(function() { + R(!h); + }, [h, R]); + return { enableToast: K ? h : false, toggleToast: x }; + }, N = function() { + var c = G().getI18n, h = (0, P.h)().enqueueNotification, R = o().enableToast, K = (0, E.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(h, this, __args); + }, 30), x = K.run, i = (0, T.d4)(function(l) { + return l.setting.layout; + }), d = (0, r.useMemo)(function() { + return i === b.a0; + }, [i]), A = (0, r.useCallback)(function(l) { + var D = l.side.toUpperCase(), Y = l.type.replace(/[-_]/g, ""); + return "".concat(D).concat(Y); + }, []), g = (0, r.useCallback)(function(l) { + if (!__notify.allowOrder(l)) return; + if (!(l.type === "LIQUIDATION" || !R)) { + var D = A(l), Y = c("".concat(D, "CancelTitle"), { defaultValue: "".concat(l.type, " ").concat(l.side, " Order Canceled") }); + x(__notify.token(l), Y); + } + }, [A, c, x, R]), C = (0, r.useCallback)(function(l) { + if (!__notify.allowOrder(l)) return; + if (!(l.type === "LIQUIDATION" || !R)) { + var D = A(l), Y = c("".concat(D, "FillTitle"), { defaultValue: "".concat(l.type, " ").concat(l.side, " Order Filled") }); + x(__notify.token(l), Y, { variant: "success" }); + } + }, [A, c, x, R]), I = (0, r.useCallback)(function(l) { + if (!__notify.allowOrder(l)) return; + if (!(l.type === "LIQUIDATION" || !R)) { + var D = A(l), Y = c("".concat(D, "PFTitle"), { defaultValue: "".concat(l.type, " ").concat(l.side, " Order Partially Filled") }); + x(__notify.token(l), Y, { variant: "success" }); + } + }, [A, c, x, R]); + return (0, r.useMemo)(function() { + return { cancelOrderNotify: g, fillOrderNotify: C, fillOrderPartNotify: I }; + }, [g, C, I]); + }; + } }[30877]; + } + }, + { + id: "30877", + original: '30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k="trd-openOrder",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)("open_order_status_toast",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(_,30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,"");return"".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"CancelTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Canceled")});L(T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"FillTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Filled")});L(T,{variant:"success"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"PFTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Partially Filled")});L(T,{variant:"success"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}', + create(__notify) { + return { 30877(V, b, e) { + e.d(b, { F: () => m, z: () => I }); + var r = e(41594), A = e.n(r), s = e(61523), f = e(92873), v = e(64041), o = e(17409), l = e(51471), P = e(40477), k = "trd-openOrder", y = function(g) { + return (0, f.o)(k, g); + }, I = function() { + var g = (0, l.zr)("open_order_status_toast", true), _ = g.data, F = g.setData, R = g.hasInitialized, L = (0, r.useCallback)(function() { + F(!_); + }, [_, F]); + return { enableToast: R ? _ : false, toggleToast: L }; + }, m = function() { + var g = y().getI18n, _ = (0, v.h)().enqueueNotification, F = I().enableToast, R = (0, P.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(_, this, __args); + }, 30), L = R.run, a = (0, s.d4)(function(i) { + return i.setting.layout; + }), M = (0, r.useMemo)(function() { + return a === o.a0; + }, [a]), C = (0, r.useCallback)(function(i) { + var S = i.side.toUpperCase(), T = i.type.replace(/[-_]/g, ""); + return "".concat(S).concat(T); + }, []), c = (0, r.useCallback)(function(i) { + if (!__notify.allowOrder(i)) return; + if (!(i.type === "LIQUIDATION" || !F)) { + var S = C(i), T = g("".concat(S, "CancelTitle"), { defaultValue: "".concat(i.type, " ").concat(i.side, " Order Canceled") }); + L(__notify.token(i), T); + } + }, [C, g, L, F]), u = (0, r.useCallback)(function(i) { + if (!__notify.allowOrder(i)) return; + if (!(i.type === "LIQUIDATION" || !F)) { + var S = C(i), T = g("".concat(S, "FillTitle"), { defaultValue: "".concat(i.type, " ").concat(i.side, " Order Filled") }); + L(__notify.token(i), T, { variant: "success" }); + } + }, [C, g, L, F]), h = (0, r.useCallback)(function(i) { + if (!__notify.allowOrder(i)) return; + if (!(i.type === "LIQUIDATION" || !F)) { + var S = C(i), T = g("".concat(S, "PFTitle"), { defaultValue: "".concat(i.type, " ").concat(i.side, " Order Partially Filled") }); + L(__notify.token(i), T, { variant: "success" }); + } + }, [C, g, L, F]); + return (0, r.useMemo)(function() { + return { cancelOrderNotify: c, fillOrderNotify: u, fillOrderPartNotify: h }; + }, [c, u, h]); + }; + } }[30877]; + } + }, + { + id: "30877", + original: '30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b="trd-openOrder",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)("open_order_status_toast",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(C,30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,"");return"".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"CancelTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Canceled")});d(u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"FillTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Filled")});d(u,{variant:"success"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"PFTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Partially Filled")});d(u,{variant:"success"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}', + create(__notify) { + return { 30877(K, T, e) { + e.d(T, { F: () => i, z: () => E }); + var t = e(41594), r = e.n(t), n = e(61523), s = e(92873), g = e(64041), c = e(17409), h = e(51471), p = e(40477), b = "trd-openOrder", S = function(y) { + return (0, s.o)(b, y); + }, E = function() { + var y = (0, h.zr)("open_order_status_toast", true), C = y.data, f = y.setData, I = y.hasInitialized, d = (0, t.useCallback)(function() { + f(!C); + }, [C, f]); + return { enableToast: I ? C : false, toggleToast: d }; + }, i = function() { + var y = S().getI18n, C = (0, g.h)().enqueueNotification, f = E().enableToast, I = (0, p.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(C, this, __args); + }, 30), d = I.run, P = (0, n.d4)(function(a) { + return a.setting.layout; + }), M = (0, t.useMemo)(function() { + return P === c.a0; + }, [P]), k = (0, t.useCallback)(function(a) { + var o = a.side.toUpperCase(), u = a.type.replace(/[-_]/g, ""); + return "".concat(o).concat(u); + }, []), A = (0, t.useCallback)(function(a) { + if (!__notify.allowOrder(a)) return; + if (!(a.type === "LIQUIDATION" || !f)) { + var o = k(a), u = y("".concat(o, "CancelTitle"), { defaultValue: "".concat(a.type, " ").concat(a.side, " Order Canceled") }); + d(__notify.token(a), u); + } + }, [k, y, d, f]), O = (0, t.useCallback)(function(a) { + if (!__notify.allowOrder(a)) return; + if (!(a.type === "LIQUIDATION" || !f)) { + var o = k(a), u = y("".concat(o, "FillTitle"), { defaultValue: "".concat(a.type, " ").concat(a.side, " Order Filled") }); + d(__notify.token(a), u, { variant: "success" }); + } + }, [k, y, d, f]), x = (0, t.useCallback)(function(a) { + if (!__notify.allowOrder(a)) return; + if (!(a.type === "LIQUIDATION" || !f)) { + var o = k(a), u = y("".concat(o, "PFTitle"), { defaultValue: "".concat(a.type, " ").concat(a.side, " Order Partially Filled") }); + d(__notify.token(a), u, { variant: "success" }); + } + }, [k, y, d, f]); + return (0, t.useMemo)(function() { + return { cancelOrderNotify: A, fillOrderNotify: O, fillOrderPartNotify: x }; + }, [A, O, x]); + }; + } }[30877]; + } + }, + { + id: "55401", + original: '55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a){var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src="".concat(x.K5,"/static/spot-trade-ui/trade-sound.mp3"),E(v),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)("div",{children:[r,(0,c.jsx)("div",{children:(0,c.jsx)("audio",{ref:h,children:(0,c.jsx)("source",{src:""})})})]})}}', + create(__notify) { + return { 55401(S, f, o) { + o.r(f), o.d(f, { SoundNotificationProvider: () => C }); + var c = o(31085), e = o(41594), x = o(17409), s = o(39116), E = function(a, __event) { + if (!__notify.canPlay(__event)) return; + var r, n; + (r = a) === null || r === void 0 || r.load(); + var u = (n = a) === null || n === void 0 ? void 0 : n.play(); + u && u.then(function() { + if (!__notify.canPlay(__event)) return; + var t; + (t = a) === null || t === void 0 || t.play(); + }).catch(function() { + }); + }, C = function(a) { + var r = a.children, n = (0, s.$$)(), u = n.interval, t = n.intervalFunc, h = (0, e.useRef)(null), N = (0, s.QZ)(), p = N.enableNotification, m = N.showNotification, d = (0, s.E$)(), l = d.notifications, P = d.lastNotifyTime, y = d.popNotification, j = (0, e.useCallback)(function() { + if (!__notify.prepareSound(l)) return; + var i, v = (i = h) === null || i === void 0 ? void 0 : i.current; + v && (v.src = "".concat(x.K5, "/static/spot-trade-ui/trade-sound.mp3"), E(v, l.current[0]), t(y)); + }, [t, y]); + return (0, e.useEffect)(function() { + var i; + (!m || !p) && (l.current = []), l && ((i = l) === null || i === void 0 ? void 0 : i.current.length) > 0 && (u.current || j()); + }, [P, p, j, l, u, m]), (0, c.jsxs)("div", { children: [r, (0, c.jsx)("div", { children: (0, c.jsx)("audio", { ref: h, children: (0, c.jsx)("source", { src: "" }) }) })] }); + }; + } }[55401]; + } + }, + { + id: "30877", + original: '30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p="trd-openOrder",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)("open_order_status_toast",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(c,30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,"");return"".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"CancelTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Canceled")});u(f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"FillTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Filled")});u(f,{variant:"success"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"PFTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Partially Filled")});u(f,{variant:"success"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}', + create(__notify) { + return { 30877(Y, T, e) { + e.d(T, { F: () => o, z: () => N }); + var a = e(41594), k = e.n(a), P = e(61523), l = e(92873), O = e(64041), C = e(17409), h = e(51471), M = e(40477), p = "trd-openOrder", R = function(n) { + return (0, l.o)(p, n); + }, N = function() { + var n = (0, h.zr)("open_order_status_toast", true), c = n.data, i = n.setData, F = n.hasInitialized, u = (0, a.useCallback)(function() { + i(!c); + }, [c, i]); + return { enableToast: F ? c : false, toggleToast: u }; + }, o = function() { + var n = R().getI18n, c = (0, O.h)().enqueueNotification, i = N().enableToast, F = (0, M.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(c, this, __args); + }, 30), u = F.run, L = (0, P.d4)(function(r) { + return r.setting.layout; + }), g = (0, a.useMemo)(function() { + return L === C.a0; + }, [L]), I = (0, a.useCallback)(function(r) { + var d = r.side.toUpperCase(), f = r.type.replace(/[-_]/g, ""); + return "".concat(d).concat(f); + }, []), S = (0, a.useCallback)(function(r) { + if (!__notify.allowOrder(r)) return; + if (!(r.type === "LIQUIDATION" || !i)) { + var d = I(r), f = n("".concat(d, "CancelTitle"), { defaultValue: "".concat(r.type, " ").concat(r.side, " Order Canceled") }); + u(__notify.token(r), f); + } + }, [I, n, u, i]), A = (0, a.useCallback)(function(r) { + if (!__notify.allowOrder(r)) return; + if (!(r.type === "LIQUIDATION" || !i)) { + var d = I(r), f = n("".concat(d, "FillTitle"), { defaultValue: "".concat(r.type, " ").concat(r.side, " Order Filled") }); + u(__notify.token(r), f, { variant: "success" }); + } + }, [I, n, u, i]), Q = (0, a.useCallback)(function(r) { + if (!__notify.allowOrder(r)) return; + if (!(r.type === "LIQUIDATION" || !i)) { + var d = I(r), f = n("".concat(d, "PFTitle"), { defaultValue: "".concat(r.type, " ").concat(r.side, " Order Partially Filled") }); + u(__notify.token(r), f, { variant: "success" }); + } + }, [I, n, u, i]); + return (0, a.useMemo)(function() { + return { cancelOrderNotify: S, fillOrderNotify: A, fillOrderPartNotify: Q }; + }, [S, A, Q]); + }; + } }[30877]; + } + }, + { + id: "30877", + original: '30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g="trd-openOrder",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)("open_order_status_toast",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(c,30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,"");return"".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"CancelTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Canceled")});n(u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"FillTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Filled")});n(u,{variant:"success"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"PFTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Partially Filled")});n(u,{variant:"success"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}', + create(__notify) { + return { 30877(L, T, e) { + e.d(T, { F: () => _, z: () => v }); + var r = e(41594), x = e.n(r), O = e(61523), h = e(92873), p = e(64041), k = e(17409), C = e(51471), I = e(40477), g = "trd-openOrder", f = function(s) { + return (0, h.o)(g, s); + }, v = function() { + var s = (0, C.zr)("open_order_status_toast", true), c = s.data, o = s.setData, d = s.hasInitialized, n = (0, r.useCallback)(function() { + o(!c); + }, [c, o]); + return { enableToast: d ? c : false, toggleToast: n }; + }, _ = function() { + var s = f().getI18n, c = (0, p.h)().enqueueNotification, o = v().enableToast, d = (0, I.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(c, this, __args); + }, 30), n = d.run, a = (0, O.d4)(function(t) { + return t.setting.layout; + }), S = (0, r.useMemo)(function() { + return a === k.a0; + }, [a]), i = (0, r.useCallback)(function(t) { + var l = t.side.toUpperCase(), u = t.type.replace(/[-_]/g, ""); + return "".concat(l).concat(u); + }, []), y = (0, r.useCallback)(function(t) { + if (!__notify.allowOrder(t)) return; + if (!(t.type === "LIQUIDATION" || !o)) { + var l = i(t), u = s("".concat(l, "CancelTitle"), { defaultValue: "".concat(t.type, " ").concat(t.side, " Order Canceled") }); + n(__notify.token(t), u); + } + }, [i, s, n, o]), F = (0, r.useCallback)(function(t) { + if (!__notify.allowOrder(t)) return; + if (!(t.type === "LIQUIDATION" || !o)) { + var l = i(t), u = s("".concat(l, "FillTitle"), { defaultValue: "".concat(t.type, " ").concat(t.side, " Order Filled") }); + n(__notify.token(t), u, { variant: "success" }); + } + }, [i, s, n, o]), m = (0, r.useCallback)(function(t) { + if (!__notify.allowOrder(t)) return; + if (!(t.type === "LIQUIDATION" || !o)) { + var l = i(t), u = s("".concat(l, "PFTitle"), { defaultValue: "".concat(t.type, " ").concat(t.side, " Order Partially Filled") }); + n(__notify.token(t), u, { variant: "success" }); + } + }, [i, s, n, o]); + return (0, r.useMemo)(function() { + return { cancelOrderNotify: y, fillOrderNotify: F, fillOrderPartNotify: m }; + }, [y, F, m]); + }; + } }[30877]; + } + }, + { + id: "30877", + original: '30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o="trd-openOrder",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)("open_order_status_toast",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(M,30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,"");return"".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"CancelTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Canceled")});_(c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"FillTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Filled")});_(c,{variant:"success"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"PFTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Partially Filled")});_(c,{variant:"success"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}', + create(__notify) { + return { 30877(R, T, e) { + e.d(T, { F: () => y, z: () => D }); + var t = e(41594), O = e.n(t), u = e(61523), i = e(92873), m = e(64041), d = e(17409), f = e(51471), a = e(40477), o = "trd-openOrder", b = function(S) { + return (0, i.o)(o, S); + }, D = function() { + var S = (0, f.zr)("open_order_status_toast", true), M = S.data, P = S.setData, N = S.hasInitialized, _ = (0, t.useCallback)(function() { + P(!M); + }, [M, P]); + return { enableToast: N ? M : false, toggleToast: _ }; + }, y = function() { + var S = b().getI18n, M = (0, m.h)().enqueueNotification, P = D().enableToast, N = (0, a.A)(function(__token, ...__args) { + if (__notify.allowToken(__token)) return Reflect.apply(M, this, __args); + }, 30), _ = N.run, v = (0, u.d4)(function(n) { + return n.setting.layout; + }), l = (0, t.useMemo)(function() { + return v === d.a0; + }, [v]), s = (0, t.useCallback)(function(n) { + var g = n.side.toUpperCase(), c = n.type.replace(/[-_]/g, ""); + return "".concat(g).concat(c); + }, []), r = (0, t.useCallback)(function(n) { + if (!__notify.allowOrder(n)) return; + if (!(n.type === "LIQUIDATION" || !P)) { + var g = s(n), c = S("".concat(g, "CancelTitle"), { defaultValue: "".concat(n.type, " ").concat(n.side, " Order Canceled") }); + _(__notify.token(n), c); + } + }, [s, S, _, P]), p = (0, t.useCallback)(function(n) { + if (!__notify.allowOrder(n)) return; + if (!(n.type === "LIQUIDATION" || !P)) { + var g = s(n), c = S("".concat(g, "FillTitle"), { defaultValue: "".concat(n.type, " ").concat(n.side, " Order Filled") }); + _(__notify.token(n), c, { variant: "success" }); + } + }, [s, S, _, P]), h = (0, t.useCallback)(function(n) { + if (!__notify.allowOrder(n)) return; + if (!(n.type === "LIQUIDATION" || !P)) { + var g = s(n), c = S("".concat(g, "PFTitle"), { defaultValue: "".concat(n.type, " ").concat(n.side, " Order Partially Filled") }); + _(__notify.token(n), c, { variant: "success" }); + } + }, [s, S, _, P]); + return (0, t.useMemo)(function() { + return { cancelOrderNotify: r, fillOrderNotify: p, fillOrderPartNotify: h }; + }, [r, p, h]); + }; + } }[30877]; + } + } + ]; + function replaceOrderNotificationFactory(id, originalFactory, bridge) { + if (typeof originalFactory !== "function") throw new TypeError("Native notification factory must be a function"); + const source = Function.prototype.toString.call(originalFactory); + const pinned = pinnedFactories.find((entry) => entry.id === String(id) && entry.original === source); + if (!pinned) throw new Error("Native notification factory source does not match a pinned public module"); + return pinned.create(bridge); + } + + // src/binance-orderbook-trade/order-notifications/scope.js + var ORDINARY_TYPES = /* @__PURE__ */ new Set([ + "LIMIT", + "MARKET", + "STOP", + "STOP_MARKET", + "TAKE_PROFIT", + "TAKE_PROFIT_MARKET", + "TRAILING_STOP_MARKET" + ]); + var ORDINARY_EXECUTIONS = /* @__PURE__ */ new Set(["NEW", "CANCELED", "EXPIRED", "TRADE", "AMENDMENT"]); + var UNSCOPED = Object.freeze({ symbol: null }); + function createOrderNotificationToken(order) { + if (!order || typeof order !== "object" || !isBinanceSymbol(order.symbol) || !ORDINARY_TYPES.has(order.type) || typeof order.clientOrderId !== "string" || order.clientOrderId.length === 0) return UNSCOPED; + if (Object.hasOwn(order, "origType") && !ORDINARY_TYPES.has(order.origType)) return UNSCOPED; + if (Object.hasOwn(order, "operate") && !ORDINARY_EXECUTIONS.has(order.operate)) return UNSCOPED; + if (order.clientOrderId.startsWith("autoclose-") || order.clientOrderId.startsWith("adl_autoclose") || order.clientOrderId.startsWith("settlement_autoclose-")) return UNSCOPED; + return Object.freeze({ symbol: order.symbol }); + } + function isOrderNotificationTokenAllowed(token, pathname) { + if (typeof token === "string") return true; + if (!token || typeof token !== "object" || token.symbol !== null && !isBinanceSymbol(token.symbol)) { + throw new TypeError("Notification scope requires a native string or a classified symbol token"); + } + const currentSymbol = parseFuturesTradingSymbolFromPathname(pathname); + return token.symbol === null || currentSymbol === null || token.symbol === currentSymbol; + } + + // src/binance-orderbook-trade/order-notifications/runtime.js + var MODULE_IDS = ["30877", "39116", "55401"]; + function createOrderNotificationScope() { + const states = Object.fromEntries(MODULE_IDS.map((id) => [id, { + status: "waiting", + reason: null, + attempts: 0, + matches: 0 + }])); + let checks = 0; + let suppressedChecks = 0; + const soundReady = () => states["39116"].status === "active" && states["55401"].status === "active"; + function allowToken(token) { + const allowed = isOrderNotificationTokenAllowed(token, self.location.pathname); + checks += 1; + if (!allowed) suppressedChecks += 1; + return allowed; + } + const bridge = Object.freeze({ + token: createOrderNotificationToken, + allowToken, + allowOrder: (order) => allowToken(createOrderNotificationToken(order)), + soundReady, + soundInput: (order) => soundReady() ? createOrderNotificationToken(order) : String(order.orderId), + allowSoundInput: (token) => !soundReady() || allowToken(token), + canPlay: (token) => !soundReady() || allowToken(token), + prepareSound(queue) { + if (!soundReady()) return true; + while (queue.current.length && !allowToken(queue.current[0])) queue.current.shift(); + return queue.current.length > 0; + } + }); + const targets = Object.fromEntries(MODULE_IDS.map((id) => [id, { + replace(original) { + states[id].attempts += 1; + const factory = replaceOrderNotificationFactory(id, original, bridge); + states[id].matches += 1; + return factory; + }, + onCapture() { + states[id].status = "active"; + }, + onFailure(reason) { + states[id].status = reason === "source_mismatch" ? "source_mismatch" : "unavailable"; + states[id].reason = reason; + } + }])); + return Object.freeze({ + targets, + snapshot() { + return { + toastActive: states["30877"].status === "active", + soundActive: soundReady(), + checks, + suppressedChecks, + modules: Object.fromEntries(MODULE_IDS.map((id) => [id, { ...states[id] }])) + }; + } + }); + } + // src/binance-orderbook-trade/chart-storage/install.js function installChartStorageOptimizer() { if (!isChartStoragePage() || Object.hasOwn(self, "__BINANCE_CHART_STORAGE__")) return; + const notifications = createOrderNotificationScope(); + Object.defineProperty(self, "__BINANCE_ORDER_NOTIFICATIONS__", { + value: Object.freeze({ snapshot: notifications.snapshot }), + configurable: true + }); Object.defineProperty(self, "__BINANCE_CHART_STORAGE__", { - value: startChartStorageOptimizer(), + value: startChartStorageOptimizer({ additionalTargets: notifications.targets }), configurable: true }); } diff --git a/scripts/generate-order-notification-modules.mjs b/scripts/generate-order-notification-modules.mjs new file mode 100644 index 0000000..a4a7095 --- /dev/null +++ b/scripts/generate-order-notification-modules.mjs @@ -0,0 +1,248 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { parse } from 'acorn'; + +const fixtureRoot = new URL('../test/fixtures/binance-order-notifications/', import.meta.url); +const sourceFile = new URL('native-factories.json', fixtureRoot); +const snapshot = await readFile(sourceFile, 'utf8'); +const sha = value => createHash('sha256').update(value).digest('hex'); +assert.equal(sha(snapshot), '10744d8bd43ff1910729cc3b81eb553014ec9f3bde6b95ccf92c004c3f4a7381', + 'The native capture changed; inspect and repin its public-source evidence first'); +const { factories } = JSON.parse(snapshot); +const notificationIds = new Set(['30877', '39116', '55401']); + +function visit(node, action) { + if (!node || typeof node !== 'object') return; + action(node); + for (const value of Object.values(node)) { + if (Array.isArray(value)) value.forEach(child => visit(child, action)); + else if (value?.type) visit(value, action); + } +} + +function matching(root, predicate) { + const found = []; + visit(root, node => { if (predicate(node)) found.push(node); }); + return found; +} + +function one(values, label) { + assert.equal(values.length, 1, `Expected one ${label}`); + return values[0]; +} + +function declarations(fn) { + return fn.body.body.filter(node => node.type === 'VariableDeclaration').flatMap(node => node.declarations); +} + +function declaration(fn, name) { + return one(declarations(fn).filter(node => node.id.name === name), `declaration ${name}`); +} + +function member(node, object, property) { + return node.type === 'MemberExpression' && !node.computed + && node.object.name === object && node.property.name === property; +} + +function calleeMember(node, object, property) { + if (node.type !== 'CallExpression') return false; + const callee = node.callee; + return callee.type === 'SequenceExpression' && callee.expressions.length === 2 + && callee.expressions[0].value === 0 && member(callee.expressions[1], object, property); +} + +function exportedFunction(factory, exportName) { + const property = one(matching(factory, node => node.type === 'Property' + && node.key.name === exportName && node.value.type === 'ArrowFunctionExpression'), `export ${exportName}`); + return declaration(factory.value, property.value.body.name).init; +} + +function importedModule(factory, id) { + const requireName = factory.value.params[2].name; + return one(declarations(factory.value).filter(node => node.init.type === 'CallExpression' + && node.init.callee.name === requireName && node.init.arguments.length === 1 + && node.init.arguments[0].value === id), `module ${id} import`).id.name; +} + +/** Every patch is an exact AST slice; reversing all patches must reproduce the capture byte for byte. */ +function transform(record) { + const original = record.source; + const ast = parse(`({${original}})`, { ecmaVersion: 'latest' }); + const factory = ast.body[0].expression.properties[0]; + assert.equal(String(factory.key.value), record.id); + const reserved = new Set(['__notify', '__token', '__args', '__event', 'Reflect']); + visit(factory, node => { + assert(node.type !== 'Identifier' || !reserved.has(node.name), 'Injected bridge or runtime identifier is shadowed'); + }); + const replacements = []; + const replace = (start, end, inserted, purpose) => replacements.push({ + factory_utf16_range: [start - 2, end - 2], + original: original.slice(start - 2, end - 2), inserted, purpose, + }); + const guard = (fn, expression, purpose) => { + assert.equal(fn.type, 'FunctionExpression'); + replace(fn.body.start + 1, fn.body.start + 1, `if(!${expression})return;`, purpose); + }; + + if (record.id === '30877') { + const hook = exportedFunction(factory, 'F'); + const throttleModule = importedModule(factory, 40477); + const throttle = one(declarations(hook).filter(node => calleeMember(node.init, throttleModule, 'A') + && node.init.arguments[1].value === 30), '30ms toast throttle'); + const enqueue = throttle.init.arguments[0]; + assert.equal(enqueue.type, 'Identifier'); + replace(enqueue.start, enqueue.end, + `function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(${enqueue.name},this,__args)}`, + 'Recheck the captured symbol token when the native toast throttle executes'); + const run = one(declarations(hook).filter(node => member(node.init, throttle.id.name, 'run')), 'toast throttle run').id.name; + for (const key of ['cancelOrderNotify', 'fillOrderNotify', 'fillOrderPartNotify']) { + const output = one(matching(hook, node => node.type === 'Property' && node.key.name === key), key); + const callback = declaration(hook, output.value.name).init.arguments[0]; + assert.equal(callback.params.length, 1); + const order = callback.params[0].name; + guard(callback, `__notify.allowOrder(${order})`, `Scope ${key} before native toast logic`); + const call = one(matching(callback, node => node.type === 'CallExpression' && node.callee.name === run), `${key} enqueue`); + replace(call.arguments[0].start, call.arguments[0].start, `__notify.token(${order}),`, `Carry the ${key} symbol token`); + } + assert.equal(replacements.length, 7); + } else if (record.id === '39116') { + const throttleModule = importedModule(factory, 40477); + const throttle = one(matching(factory, node => calleeMember(node, throttleModule, 'A') + && node.arguments[1].value === 500), '500ms sound throttle'); + const callback = throttle.arguments[0]; + assert.equal(callback.type, 'Identifier'); + replace(callback.start, callback.end, + `function(__event){if(__notify.allowSoundInput(__event))return Reflect.apply(${callback.name},this,[__event])}`, + 'Recheck a ready sound token before the native throttle callback enqueues it'); + const streamHook = one(declarations(factory.value).filter(node => node.init.type === 'FunctionExpression' + && node.init.start < throttle.start && node.init.end > throttle.end), 'sound stream hook').init; + const throttleDeclaration = one(declarations(streamHook).filter(node => node.init === throttle), 'sound throttle declaration'); + const run = one(declarations(streamHook).filter(node => member(node.init, throttleDeclaration.id.name, 'run')), 'sound throttle run').id.name; + const calls = matching(streamHook, node => node.type === 'CallExpression' && node.callee.name === run); + assert.equal(calls.length, 2); + for (const call of calls) { + assert.equal(call.arguments.length, 1); + const stringify = call.arguments[0]; + assert.equal(stringify.callee.name, 'String'); + assert.equal(stringify.arguments.length, 1); + const orderId = stringify.arguments[0]; + assert.equal(orderId.type, 'MemberExpression'); + assert.equal(orderId.property.name, 'orderId'); + const order = orderId.object.name; + replace(call.start, call.end, + `((!__notify.soundReady()||__notify.allowOrder(${order}))&&${run}(__notify.soundInput(${order})))`, + 'Keep unready native order IDs; otherwise scope and capture the stream order symbol'); + } + assert.equal(replacements.length, 3); + } else { + assert.equal(record.id, '55401'); + const player = one(declarations(factory.value).filter(node => node.init.type === 'FunctionExpression' + && matching(node.init, child => child.type === 'MemberExpression' && child.property.name === 'load').length === 1), 'native audio player'); + const play = player.init; + assert.equal(play.params.length, 1); + replace(play.params[0].end, play.params[0].end, ',__event', 'Capture one symbol token for both native play calls'); + guard(play, '__notify.canPlay(__event)', 'Recheck symbol immediately before audio load and play'); + const then = one(matching(play, node => node.type === 'CallExpression' + && node.callee.type === 'MemberExpression' && node.callee.property.name === 'then'), 'native audio promise continuation'); + guard(then.arguments[0], '__notify.canPlay(__event)', 'Recheck the same symbol token before the second native play'); + const provider = exportedFunction(factory, 'SoundNotificationProvider'); + const queue = one(declarations(provider).filter(node => node.init.type === 'MemberExpression' + && node.init.property.name === 'notifications'), 'native notification queue').id.name; + const callback = one(matching(provider, node => node.type === 'FunctionExpression' + && matching(node, child => child.type === 'CallExpression' && child.callee.name === player.id.name).length === 1) + .filter(node => node !== provider), 'native playback callback'); + guard(callback, `__notify.prepareSound(${queue})`, 'Drop stale queued symbols only when the native playback entrypoint is ready'); + const call = one(matching(callback, node => node.type === 'CallExpression' && node.callee.name === player.id.name), 'native playback invocation'); + assert.equal(call.arguments.length, 1); + replace(call.arguments[0].end, call.arguments[0].end, `,${queue}.current[0]`, 'Retain the active queue token across the audio promise'); + assert.equal(replacements.length, 5); + } + + replacements.sort((a, b) => a.factory_utf16_range[0] - b.factory_utf16_range[0]); + let transformed = ''; + let previousEnd = 0; + for (const replacement of replacements) { + const [start, end] = replacement.factory_utf16_range; + assert(start >= previousEnd, 'Overlapping native notification replacements'); + transformed += original.slice(previousEnd, start); + replacement.transformed_utf16_range = [transformed.length, transformed.length + replacement.inserted.length]; + transformed += replacement.inserted; + previousEnd = end; + } + transformed += original.slice(previousEnd); + let restored = transformed; + for (const replacement of [...replacements].reverse()) { + const [start, end] = replacement.transformed_utf16_range; + assert.equal(restored.slice(start, end), replacement.inserted); + restored = restored.slice(0, start) + replacement.original + restored.slice(end); + } + assert.equal(restored, original, 'Inverse replacement must exactly restore native source'); + parse(`({${transformed}})`, { ecmaVersion: 'latest' }); + return { ...record, transformed, replacements }; +} + +for (const record of factories) assert.equal(sha(record.source), record.sha256); +const modified = factories.filter(record => notificationIds.has(record.id)).map(transform); +assert.equal(modified.length, 8); +assert.equal(modified.filter(record => record.id === '30877').length, 6); + +const moduleSource = `/* Generated by scripts/generate-order-notification-modules.mjs; see notification-manifest.json. */ +const pinnedFactories = [ +${modified.map(record => ` { + id: ${JSON.stringify(record.id)}, + original: ${JSON.stringify(record.source)}, + create(__notify) { return {${record.transformed}}[${record.id}]; }, + }`).join(',\n')} +]; + +/** Preserve the complete native factories and alter only the verified notification side effects. */ +export function replaceOrderNotificationFactory(id, originalFactory, bridge) { + if (typeof originalFactory !== 'function') throw new TypeError('Native notification factory must be a function'); + const source = Function.prototype.toString.call(originalFactory); + const pinned = pinnedFactories.find(entry => entry.id === String(id) && entry.original === source); + if (!pinned) throw new Error('Native notification factory source does not match a pinned public module'); + return pinned.create(bridge); +} +`; + +const fixtureSource = `/* Complete native factory sources, unchanged from the public runtime capture. */ +export const nativeOrderNotificationSources = ${JSON.stringify(factories.map(({ id, chunks, source }) => ({ id, chunks, source })), null, 2)}; + +const nativeFactories = [ +${factories.map(({ id, chunks, source }) => ` { id: ${JSON.stringify(id)}, chunkId: ${JSON.stringify(chunks[0])}, factory: {${source}}[${id}] }`).join(',\n')} +]; + +/** Select a captured factory without evaluating source strings. */ +export function createNativeOrderNotificationFactory(id, chunkId) { + const matches = nativeFactories.filter(entry => entry.id === String(id) && (chunkId === undefined || entry.chunkId === String(chunkId))); + if (matches.length !== 1) throw new Error('Native notification fixture needs one exact module and chunk'); + return matches[0].factory; +} +`; +parse(moduleSource, { ecmaVersion: 'latest', sourceType: 'module' }); +parse(fixtureSource, { ecmaVersion: 'latest', sourceType: 'module' }); +const output = new URL('../src/binance-orderbook-trade/order-notifications/native-modules.js', import.meta.url); +await mkdir(new URL('.', output), { recursive: true }); +await writeFile(output, moduleSource); +await writeFile(new URL('original-factories.js', fixtureRoot), fixtureSource); +const manifest = { + schema_version: 1, + capture_date: '2026-10-07', + source_snapshot: { file: 'native-factories.json', sha256: sha(snapshot) }, + parser: 'acorn@8.18.0', + offsets: 'Zero-based UTF-16 offsets; end is exclusive. Factory ranges refer to exact Function.prototype.toString sources.', + scope: 'Only native order toast and sound side effects are scoped. Native subscriptions, event emitters, preferences, queue limit, throttles, cooldown, JSX and other factory behavior remain unchanged.', + factories: factories.map(record => { + const patch = modified.find(candidate => candidate.id === record.id && candidate.chunks[0] === record.chunks[0]); + return { + id: record.id, chunks: record.chunks, evidence: record.evidence, bundle: record.bundle, + original_sha256: record.sha256, + ...(patch ? { transformed_sha256: sha(patch.transformed), inverse_replacement_verified: true, replacements: patch.replacements } + : { transformation: 'None; retained as an independent native dependency fixture.' }), + }; + }), + outputs: { module_sha256: sha(moduleSource), fixture_sha256: sha(fixtureSource) }, +}; +await writeFile(new URL('notification-manifest.json', fixtureRoot), JSON.stringify(manifest, null, 2) + '\n'); +console.log(JSON.stringify({ pinned_factories: modified.length, native_fixtures: factories.length, inverse_replacement_verified: true, outputs: manifest.outputs })); diff --git a/src/binance-orderbook-trade/chart-storage/install.js b/src/binance-orderbook-trade/chart-storage/install.js index ff71d4c..641dff8 100644 --- a/src/binance-orderbook-trade/chart-storage/install.js +++ b/src/binance-orderbook-trade/chart-storage/install.js @@ -1,10 +1,15 @@ import { startChartStorageOptimizer } from './runtime.js'; import { isChartStoragePage } from './scope.js'; +import { createOrderNotificationScope } from '../order-notifications/runtime.js'; /** Run before orderbook initialization without widening its frame or route policy. */ export function installChartStorageOptimizer() { if (!isChartStoragePage() || Object.hasOwn(self, '__BINANCE_CHART_STORAGE__')) return; + const notifications = createOrderNotificationScope(); + Object.defineProperty(self, '__BINANCE_ORDER_NOTIFICATIONS__', { + value: Object.freeze({ snapshot: notifications.snapshot }), configurable: true, + }); Object.defineProperty(self, '__BINANCE_CHART_STORAGE__', { - value: startChartStorageOptimizer(), configurable: true, + value: startChartStorageOptimizer({ additionalTargets: notifications.targets }), configurable: true, }); } diff --git a/src/binance-orderbook-trade/chart-storage/runtime.js b/src/binance-orderbook-trade/chart-storage/runtime.js index 29cdaaa..5ff0cb8 100644 --- a/src/binance-orderbook-trade/chart-storage/runtime.js +++ b/src/binance-orderbook-trade/chart-storage/runtime.js @@ -12,8 +12,14 @@ const DRAWING_MODULE = '76535'; * Mirror optimization is optional and drainable. Drawing ownership protection * remains installed after stop and gets the same finite startup capture window. */ -export function startChartStorageOptimizer() { +export function startChartStorageOptimizer({ additionalTargets = {} } = {}) { if (!isChartStoragePage()) throw new Error('Chart storage requires a top-level Binance trading page'); + for (const [id, target] of Object.entries(additionalTargets)) { + if (id === MIRROR_MODULE || id === DRAWING_MODULE || + !target || [target.replace, target.onCapture, target.onFailure].some(callback => typeof callback !== 'function')) { + throw new Error('Additional native targets require independent replacement and outcome callbacks'); + } + } const writer = createChartMirrorWriter(); const state = { status: 'waiting', reason: null, attempts: 0, matches: 0, executions: 0 }; const drawingScope = { status: 'waiting', reason: null, attempts: 0, matches: 0, executions: 0 }; @@ -78,12 +84,28 @@ export function startChartStorageOptimizer() { targets: { [MIRROR_MODULE]: original => replaceFactory(MIRROR_MODULE, original, factory => replaceChartMirrorFactory(factory, dispatch)), [DRAWING_MODULE]: original => replaceFactory(DRAWING_MODULE, original, replaceChartDrawingSaveFactory), + ...Object.fromEntries(Object.entries(additionalTargets).map(([id, target]) => [id, original => { + if (!isChartStoragePage()) { + observer.stop('scope_changed'); + void stop('scope_changed'); + return original; + } + return target.replace(original); + }])), }, onCapture(id) { + if (Object.hasOwn(additionalTargets, id)) { + additionalTargets[id].onCapture(); + return; + } const targetState = id === MIRROR_MODULE ? state : drawingScope; if (id !== MIRROR_MODULE || !stopping) targetState.status = 'active'; }, onFailure(id, reason) { + if (Object.hasOwn(additionalTargets, id)) { + additionalTargets[id].onFailure(reason); + return; + } if (id === MIRROR_MODULE) { void stop(reason === 'execution_failed' ? 'capture_failed' : reason); } else { @@ -95,6 +117,7 @@ export function startChartStorageOptimizer() { }); } catch { // A late extension cannot safely replace the runtime's cached modules. + for (const target of Object.values(additionalTargets)) target.onFailure('bootstrap_unavailable'); drawingScope.status = 'unavailable'; drawingScope.reason = 'bootstrap_unavailable'; finishObservation(); diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js index 3d5c982..8e52e62 100644 --- a/src/binance-orderbook-trade/index.user.js +++ b/src/binance-orderbook-trade/index.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.219 +// @version 2.7.220 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* diff --git a/src/binance-orderbook-trade/order-notifications/native-modules.js b/src/binance-orderbook-trade/order-notifications/native-modules.js new file mode 100644 index 0000000..8a31ebd --- /dev/null +++ b/src/binance-orderbook-trade/order-notifications/native-modules.js @@ -0,0 +1,52 @@ +/* Generated by scripts/generate-order-notification-modules.mjs; see notification-manifest.json. */ +const pinnedFactories = [ + { + id: "39116", + original: "39116(D,l,t){\"use strict\";t.d(l,{QZ:()=>p.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(A,500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length)p.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(function(__event){if(__notify.allowSoundInput(__event))return Reflect.apply(A,this,[__event])},500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&((!__notify.soundReady()||__notify.allowOrder(P))&&u(__notify.soundInput(P)))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&((!__notify.soundReady()||__notify.allowOrder(P))&&u(__notify.soundInput(P)))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length)N,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$=\"trd-openOrder\",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)(\"open_order_status_toast\",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(h,30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,\"\");return\"\".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"CancelTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Canceled\")});x(Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"FillTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"PFTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Partially Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}", + create(__notify) { return {30877(be,W,e){e.d(W,{F:()=>N,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$="trd-openOrder",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)("open_order_status_toast",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(h,this,__args)},30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,"");return"".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!__notify.allowOrder(l))return;if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"CancelTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Canceled")});x(__notify.token(l),Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!__notify.allowOrder(l))return;if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"FillTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Filled")});x(__notify.token(l),Y,{variant:"success"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!__notify.allowOrder(l))return;if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"PFTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Partially Filled")});x(__notify.token(l),Y,{variant:"success"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}}[30877]; }, + }, + { + id: "30877", + original: "30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k=\"trd-openOrder\",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)(\"open_order_status_toast\",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(_,30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,\"\");return\"\".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"CancelTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Canceled\")});L(T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"FillTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"PFTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Partially Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}", + create(__notify) { return {30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k="trd-openOrder",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)("open_order_status_toast",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(_,this,__args)},30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,"");return"".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!__notify.allowOrder(i))return;if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"CancelTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Canceled")});L(__notify.token(i),T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!__notify.allowOrder(i))return;if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"FillTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Filled")});L(__notify.token(i),T,{variant:"success"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!__notify.allowOrder(i))return;if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"PFTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Partially Filled")});L(__notify.token(i),T,{variant:"success"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}}[30877]; }, + }, + { + id: "30877", + original: "30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b=\"trd-openOrder\",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)(\"open_order_status_toast\",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(C,30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,\"\");return\"\".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"CancelTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Canceled\")});d(u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"FillTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"PFTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Partially Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}", + create(__notify) { return {30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b="trd-openOrder",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)("open_order_status_toast",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(C,this,__args)},30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,"");return"".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!__notify.allowOrder(a))return;if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"CancelTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Canceled")});d(__notify.token(a),u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!__notify.allowOrder(a))return;if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"FillTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Filled")});d(__notify.token(a),u,{variant:"success"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!__notify.allowOrder(a))return;if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"PFTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Partially Filled")});d(__notify.token(a),u,{variant:"success"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}}[30877]; }, + }, + { + id: "55401", + original: "55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a){var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src=\"\".concat(x.K5,\"/static/spot-trade-ui/trade-sound.mp3\"),E(v),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)(\"div\",{children:[r,(0,c.jsx)(\"div\",{children:(0,c.jsx)(\"audio\",{ref:h,children:(0,c.jsx)(\"source\",{src:\"\"})})})]})}}", + create(__notify) { return {55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a,__event){if(!__notify.canPlay(__event))return;var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){if(!__notify.canPlay(__event))return;var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){if(!__notify.prepareSound(l))return;var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src="".concat(x.K5,"/static/spot-trade-ui/trade-sound.mp3"),E(v,l.current[0]),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)("div",{children:[r,(0,c.jsx)("div",{children:(0,c.jsx)("audio",{ref:h,children:(0,c.jsx)("source",{src:""})})})]})}}}[55401]; }, + }, + { + id: "30877", + original: "30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p=\"trd-openOrder\",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)(\"open_order_status_toast\",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(c,30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,\"\");return\"\".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"CancelTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Canceled\")});u(f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"FillTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"PFTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Partially Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}", + create(__notify) { return {30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p="trd-openOrder",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)("open_order_status_toast",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(c,this,__args)},30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,"");return"".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!__notify.allowOrder(r))return;if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"CancelTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Canceled")});u(__notify.token(r),f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!__notify.allowOrder(r))return;if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"FillTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Filled")});u(__notify.token(r),f,{variant:"success"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!__notify.allowOrder(r))return;if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"PFTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Partially Filled")});u(__notify.token(r),f,{variant:"success"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}}[30877]; }, + }, + { + id: "30877", + original: "30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g=\"trd-openOrder\",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)(\"open_order_status_toast\",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(c,30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,\"\");return\"\".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"CancelTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Canceled\")});n(u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"FillTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"PFTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Partially Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}", + create(__notify) { return {30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g="trd-openOrder",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)("open_order_status_toast",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(c,this,__args)},30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,"");return"".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!__notify.allowOrder(t))return;if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"CancelTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Canceled")});n(__notify.token(t),u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!__notify.allowOrder(t))return;if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"FillTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Filled")});n(__notify.token(t),u,{variant:"success"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!__notify.allowOrder(t))return;if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"PFTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Partially Filled")});n(__notify.token(t),u,{variant:"success"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}}[30877]; }, + }, + { + id: "30877", + original: "30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o=\"trd-openOrder\",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)(\"open_order_status_toast\",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(M,30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,\"\");return\"\".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"CancelTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Canceled\")});_(c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"FillTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"PFTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Partially Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}", + create(__notify) { return {30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o="trd-openOrder",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)("open_order_status_toast",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(function(__token,...__args){if(__notify.allowToken(__token))return Reflect.apply(M,this,__args)},30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,"");return"".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!__notify.allowOrder(n))return;if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"CancelTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Canceled")});_(__notify.token(n),c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!__notify.allowOrder(n))return;if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"FillTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Filled")});_(__notify.token(n),c,{variant:"success"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!__notify.allowOrder(n))return;if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"PFTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Partially Filled")});_(__notify.token(n),c,{variant:"success"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}}[30877]; }, + } +]; + +/** Preserve the complete native factories and alter only the verified notification side effects. */ +export function replaceOrderNotificationFactory(id, originalFactory, bridge) { + if (typeof originalFactory !== 'function') throw new TypeError('Native notification factory must be a function'); + const source = Function.prototype.toString.call(originalFactory); + const pinned = pinnedFactories.find(entry => entry.id === String(id) && entry.original === source); + if (!pinned) throw new Error('Native notification factory source does not match a pinned public module'); + return pinned.create(bridge); +} diff --git a/src/binance-orderbook-trade/order-notifications/runtime.js b/src/binance-orderbook-trade/order-notifications/runtime.js new file mode 100644 index 0000000..a0f21f3 --- /dev/null +++ b/src/binance-orderbook-trade/order-notifications/runtime.js @@ -0,0 +1,67 @@ +import { replaceOrderNotificationFactory } from './native-modules.js'; +import { createOrderNotificationToken, isOrderNotificationTokenAllowed } from './scope.js'; + +const MODULE_IDS = ['30877', '39116', '55401']; + +/** + * Share the orderbook's single early queue observer without sharing storage + * state. Producer and player must both execute their pinned factories before + * either side changes the native string queue contract. + */ +export function createOrderNotificationScope() { + const states = Object.fromEntries(MODULE_IDS.map(id => [id, { + status: 'waiting', reason: null, attempts: 0, matches: 0, + }])); + let checks = 0; + let suppressedChecks = 0; + const soundReady = () => states['39116'].status === 'active' && states['55401'].status === 'active'; + + function allowToken(token) { + const allowed = isOrderNotificationTokenAllowed(token, self.location.pathname); + checks += 1; + if (!allowed) suppressedChecks += 1; + return allowed; + } + + const bridge = Object.freeze({ + token: createOrderNotificationToken, + allowToken, + allowOrder: order => allowToken(createOrderNotificationToken(order)), + soundReady, + soundInput: order => soundReady() ? createOrderNotificationToken(order) : String(order.orderId), + allowSoundInput: token => !soundReady() || allowToken(token), + canPlay: token => !soundReady() || allowToken(token), + prepareSound(queue) { + if (!soundReady()) return true; + // Native j() runs only after its cooldown ends. Removing the in-flight + // head sooner would make the scheduled pop delete the next notification. + while (queue.current.length && !allowToken(queue.current[0])) queue.current.shift(); + return queue.current.length > 0; + }, + }); + + const targets = Object.fromEntries(MODULE_IDS.map(id => [id, { + replace(original) { + states[id].attempts += 1; + const factory = replaceOrderNotificationFactory(id, original, bridge); + states[id].matches += 1; + return factory; + }, + onCapture() { states[id].status = 'active'; }, + onFailure(reason) { + states[id].status = reason === 'source_mismatch' ? 'source_mismatch' : 'unavailable'; + states[id].reason = reason; + }, + }])); + + return Object.freeze({ + targets, + snapshot() { + return { + toastActive: states['30877'].status === 'active', soundActive: soundReady(), + checks, suppressedChecks, + modules: Object.fromEntries(MODULE_IDS.map(id => [id, { ...states[id] }])), + }; + }, + }); +} diff --git a/src/binance-orderbook-trade/order-notifications/scope.js b/src/binance-orderbook-trade/order-notifications/scope.js new file mode 100644 index 0000000..71f7286 --- /dev/null +++ b/src/binance-orderbook-trade/order-notifications/scope.js @@ -0,0 +1,39 @@ +import { isBinanceSymbol } from '../../shared/binance-symbol.js'; +import { parseFuturesTradingSymbolFromPathname } from '../../shared/binance-futures-route.js'; + +const ORDINARY_TYPES = new Set([ + 'LIMIT', 'MARKET', 'STOP', 'STOP_MARKET', 'TAKE_PROFIT', + 'TAKE_PROFIT_MARKET', 'TRAILING_STOP_MARKET', +]); +const ORDINARY_EXECUTIONS = new Set(['NEW', 'CANCELED', 'EXPIRED', 'TRADE', 'AMENDMENT']); +const UNSCOPED = Object.freeze({ symbol: null }); + +/** + * Preserve system/unknown events. Native toast callbacks receive cached REST + * orders, which need not contain the stream-only operate or origType fields. + * Only the symbol survives the native debounce; no order identifiers are kept. + */ +export function createOrderNotificationToken(order) { + if (!order || typeof order !== 'object' || !isBinanceSymbol(order.symbol) + || !ORDINARY_TYPES.has(order.type) + || typeof order.clientOrderId !== 'string' || order.clientOrderId.length === 0) return UNSCOPED; + if (Object.hasOwn(order, 'origType') && !ORDINARY_TYPES.has(order.origType)) return UNSCOPED; + if (Object.hasOwn(order, 'operate') && !ORDINARY_EXECUTIONS.has(order.operate)) return UNSCOPED; + if (order.clientOrderId.startsWith('autoclose-') + || order.clientOrderId.startsWith('adl_autoclose') + || order.clientOrderId.startsWith('settlement_autoclose-')) return UNSCOPED; + return Object.freeze({ symbol: order.symbol }); +} + +/** + * Strings are accepted native queue entries from before the two sound factories + * jointly activate. They have no reliable symbol and retain their native path. + */ +export function isOrderNotificationTokenAllowed(token, pathname) { + if (typeof token === 'string') return true; + if (!token || typeof token !== 'object' || (token.symbol !== null && !isBinanceSymbol(token.symbol))) { + throw new TypeError('Notification scope requires a native string or a classified symbol token'); + } + const currentSymbol = parseFuturesTradingSymbolFromPathname(pathname); + return token.symbol === null || currentSymbol === null || token.symbol === currentSymbol; +} diff --git a/test/dom/binance-orderbook-trade/order-notifications.test.js b/test/dom/binance-orderbook-trade/order-notifications.test.js new file mode 100644 index 0000000..d32336e --- /dev/null +++ b/test/dom/binance-orderbook-trade/order-notifications.test.js @@ -0,0 +1,486 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + attachNativeOrderEventHost, + createNativeNotificationModules, + createNativeOrderDecoder, + createNotificationHarness, + createNotificationStreams, + ordinaryNotificationOrder, +} from '../../helpers/binance-order-notifications.js'; + +test('user receives no foreign-symbol order toast or sound in a futures tab', async (t) => { + // Given the active futures page displays BTC while native reminders are enabled + const harness = await createNotificationHarness(t); + const order = ordinaryNotificationOrder({ symbol: 'ETHUSDT' }); + + // When the native order event and sound stream deliver another symbol + await harness.notify(order); + await harness.advance(30); + await harness.advance(470); + + // Then the current tab has no unrelated visual or audible reminder + assert.deepEqual({ toasts: harness.toasts, audio: harness.audio }, { toasts: [], audio: [] }); +}); + +test('user host fixture reproduces the native foreign-symbol toast and sound', async (t) => { + // Given the captured unmodified factories run in the real React host + const harness = await createNotificationHarness(t, { patched: false, inspectSoundQueue: true }); + const order = ordinaryNotificationOrder({ symbol: 'ETHUSDT' }); + + // When an unrelated native order completes through the SDK and cached-order adapter + await harness.notify(order); + await harness.advance(500); + + // Then the baseline delivers its original message and string queue entry without filtering + assert.deepEqual(harness.toasts, [{ message: 'LIMIT BUY Order Filled', options: { variant: 'success' } }]); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + assert.deepEqual(harness.queueSnapshot(), ['42']); + assert.deepEqual(harness.nativeHost.orders(), []); +}); + +test('user receives the current-symbol toast and sound at the original native deadlines', async (t) => { + // Given native notifications are enabled for the displayed futures symbol + const harness = await createNotificationHarness(t); + + // When a current-symbol fill arrives just before the native toast deadline + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(29); + + // Then both native presentation boundaries are still idle + assert.deepEqual(harness.toasts, []); + assert.deepEqual(harness.audio, []); + + // When the native thirty-millisecond toast deadline is reached + await harness.advance(1); + + // Then the native message is enqueued with its success variant + assert.deepEqual(harness.toasts, [{ message: 'LIMIT BUY Order Filled', options: { variant: 'success' } }]); + assert.deepEqual(harness.audio, []); + + // When the sound debounce remains one millisecond short + await harness.advance(469); + + // Then audio has not played early + assert.deepEqual(harness.audio, []); + + // When the original five-hundred-millisecond sound deadline is reached + await harness.advance(1); + + // Then native audio loading and both native play calls remain intact + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); +}); + +for (const variantChunk of ['37511', '24132', '57898', '57276', '76905', '49426']) { + test(`user receives only current-symbol toasts with native chunk ${variantChunk}`, async (t) => { + // Given this captured native toast variant is mounted with sound disabled + const harness = await createNotificationHarness(t, { variantChunk, soundEnabled: false }); + + // When a foreign fill and then a current-symbol cancellation arrive + await harness.notify(ordinaryNotificationOrder({ symbol: 'ETHUSDT' })); + await harness.advance(30); + await harness.notify(ordinaryNotificationOrder({ status: 'CANCELED', orderId: 43 })); + await harness.advance(30); + + // Then only the native cancellation text is delivered + assert.deepEqual(harness.toasts, [{ message: 'LIMIT BUY Order Canceled', options: undefined }]); + assert.deepEqual(harness.audio, []); + }); +} + +for (const symbol of ['BTCUSDT', 'ETHUSDT']) { + test(`user sees reminders only for ${symbol} in an independently mounted futures page`, async (t) => { + // Given this page has its own route, native state and notification provider + const harness = await createNotificationHarness(t, { pathname: `/en/futures/${symbol}` }); + const foreignSymbol = symbol === 'BTCUSDT' ? 'ETHUSDT' : 'BTCUSDT'; + + // When both account symbols deliver their native completion events + await harness.notify(ordinaryNotificationOrder({ symbol: foreignSymbol, orderId: 43 })); + await harness.advance(500); + await harness.notify(ordinaryNotificationOrder({ symbol })); + await harness.advance(500); + + // Then this page presents exactly its own order reminder + assert.equal(harness.toasts.length, 1); + assert.deepEqual(harness.audio, [ + { kind: 'load', pathname: `/en/futures/${symbol}` }, + { kind: 'play', pathname: `/en/futures/${symbol}` }, + { kind: 'play', pathname: `/en/futures/${symbol}` }, + ]); + }); +} + +test('user filters ordinary coin-margined events received by a USDT futures tab', async (t) => { + // Given native hooks subscribe to both account streams on the BTCUSDT page + const harness = await createNotificationHarness(t); + + // When the coin-margined stream delivers an ordinary BTCUSD perpetual fill + await harness.notify(ordinaryNotificationOrder({ symbol: 'BTCUSD_PERP' }), { isCM: true }); + await harness.advance(500); + + // Then neither toast nor audio leaks from that other symbol + assert.deepEqual({ toasts: harness.toasts, audio: harness.audio }, { toasts: [], audio: [] }); + assert.equal(harness.streams.subscriptions.some((entry) => entry.isCM), true); +}); + +test('user keeps native reminders on a coin-margined route outside supported USD-M routes', async (t) => { + // Given the loaded host displays a coin-margined route the USD-M parser does not recognize + const harness = await createNotificationHarness(t, { pathname: '/en/futures/BTCUSD_PERP' }); + + // When that native coin-margined order fills + await harness.notify(ordinaryNotificationOrder({ symbol: 'BTCUSD_PERP' }), { isCM: true }); + await harness.advance(500); + + // Then the matching symbol keeps the original toast and native sound + assert.deepEqual(harness.toasts, [{ message: 'LIMIT BUY Order Filled', options: { variant: 'success' } }]); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); +}); + +for (const outcome of [ + { status: 'PARTIALLY_FILLED', operate: 'TRADE', text: 'Partially Filled', sound: true }, + { status: 'EXPIRED', operate: 'EXPIRED', text: 'Canceled', sound: true }, + { status: 'EXPIRED_IN_MATCH', operate: 'EXPIRED', text: 'Canceled', sound: false }, +]) { + test(`user retains native ${outcome.status} reminders only for the current symbol`, async (t) => { + // Given the current and foreign orders have the same native execution outcome + const harness = await createNotificationHarness(t); + const nativeEvent = { status: outcome.status, operate: outcome.operate }; + + // When the foreign event is followed by the matching-symbol event + await harness.notify(ordinaryNotificationOrder({ ...nativeEvent, symbol: 'ETHUSDT', orderId: 43 })); + await harness.advance(500); + await harness.notify(ordinaryNotificationOrder(nativeEvent)); + await harness.advance(500); + + // Then only the matching-symbol event keeps the original status-specific presentation + assert.deepEqual(harness.toasts.map((toast) => toast.message), [`LIMIT BUY Order ${outcome.text}`]); + assert.deepEqual(harness.audio.map((event) => event.kind), outcome.sound ? ['load', 'play', 'play'] : []); + }); +} + +for (const types of ['LIMIT', 'MARKET', 'STOP', 'STOP_MARKET', 'TAKE_PROFIT', 'TAKE_PROFIT_MARKET', 'TRAILING_STOP_MARKET']) { + test(`user filters foreign-symbol ordinary ${types} reminders`, async (t) => { + // Given the order carries one of the observed ordinary Binance type discriminators + const harness = await createNotificationHarness(t); + const order = ordinaryNotificationOrder({ symbol: 'ETHUSDT', type: types, orderType: types, origType: types }); + + // When its completion reaches the real native toast and audio paths + await harness.notify(order); + await harness.advance(500); + + // Then this ordinary foreign-symbol event has no visible or audible effect + assert.deepEqual({ toasts: harness.toasts, audio: harness.audio }, { toasts: [], audio: [] }); + }); +} + +for (const isCM of [false, true]) { + test(`user keeps the matching sound when a native ${isCM ? 'CM' : 'UM'} batch ends with another symbol`, async (t) => { + // Given a single native batch contains a matching event followed by a foreign event + const harness = await createNotificationHarness(t); + const matching = ordinaryNotificationOrder(); + const foreign = ordinaryNotificationOrder({ symbol: 'ETHUSDT', orderId: 43 }); + + // When the account stream delivers both events during the same debounce window + await harness.emitStream([matching, foreign], { isCM }); + await harness.advance(500); + + // Then filtering does not let the unrelated trailing event erase the matching sound + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + }); +} + +for (const risk of [ + { name: 'liquidation type', fields: { type: 'LIQUIDATION', orderType: 'LIQUIDATION' }, toast: false }, + { name: 'calculated execution', fields: { operate: 'CALCULATED' }, toast: true }, + { name: 'autoclose client identifier', fields: { clientOrderId: 'autoclose-123' }, toast: true }, + { name: 'ADL client identifier', fields: { clientOrderId: 'adl_autoclose-123' }, toast: true }, + { name: 'settlement client identifier', fields: { clientOrderId: 'settlement_autoclose-123' }, toast: true }, +]) { + test(`user retains native risk behavior for a foreign-symbol ${risk.name}`, async (t) => { + // Given a foreign symbol carries this native risk discriminator + const harness = await createNotificationHarness(t); + const order = ordinaryNotificationOrder({ symbol: 'ETHUSDT', ...risk.fields }); + + // When the risk event reaches both captured native presentation paths + await harness.notify(order); + await harness.advance(500); + + // Then filtering preserves the original risk toast policy and native sound + assert.equal(harness.toasts.length, risk.toast ? 1 : 0); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + }); +} + +for (const unknown of [ + { name: 'missing client identifier', fields: { clientOrderId: undefined } }, + { name: 'missing symbol', fields: { symbol: undefined } }, + { name: 'malformed symbol', fields: { symbol: 'ETH/USDT' } }, + { name: 'unknown order type', fields: { type: 'UNRECOGNIZED', orderType: 'UNRECOGNIZED' } }, + { name: 'unknown original type', fields: { origType: 'UNRECOGNIZED' } }, + { name: 'unknown execution operation', fields: { operate: 'UNRECOGNIZED' } }, +]) { + test(`user retains native reminders for an order with ${unknown.name}`, async (t) => { + // Given an event cannot be proven to be an ordinary foreign-symbol order + const harness = await createNotificationHarness(t); + const order = ordinaryNotificationOrder({ symbol: 'ETHUSDT', ...unknown.fields }); + + // When the uncertain payload arrives through both original native paths + await harness.notify(order); + await harness.advance(500); + + // Then its native toast and audio behavior remain visible + assert.equal(harness.toasts.length, 1); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + }); +} + +for (const preferences of [ + { toastEnabled: false, soundEnabled: true }, + { toastEnabled: true, soundEnabled: false }, + { toastEnabled: false, soundEnabled: false }, +]) { + test(`user keeps native reminder preferences toast=${preferences.toastEnabled} sound=${preferences.soundEnabled}`, async (t) => { + // Given real native preference hooks initialize with the selected switches + const harness = await createNotificationHarness(t, preferences); + + // When the displayed symbol fills after preference initialization + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(500); + + // Then each original native switch independently controls its presentation + assert.equal(harness.toasts.length, preferences.toastEnabled ? 1 : 0); + assert.deepEqual(harness.audio.map((event) => event.kind), preferences.soundEnabled ? ['load', 'play', 'play'] : []); + }); +} + +test('user sees no stale toast after switching symbol during the native toast delay', async (t) => { + // Given a current-symbol fill is waiting in the native thirty-millisecond throttle + const harness = await createNotificationHarness(t); + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(29); + + // When the page switches symbols before the pending notification is enqueued + harness.navigate('/en/futures/ETHUSDT'); + await harness.advance(1); + await harness.advance(470); + + // Then both delayed presentation paths recheck the current route + assert.deepEqual({ toasts: harness.toasts, audio: harness.audio }, { toasts: [], audio: [] }); +}); + +test('user hears no stale audio after switching symbol during the native sound delay', async (t) => { + // Given a current-symbol fill has shown its toast but audio has not started + const harness = await createNotificationHarness(t); + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(499); + assert.equal(harness.toasts.length, 1); + + // When the page switches symbols before the native audio deadline + harness.navigate('/en/futures/ETHUSDT'); + await harness.advance(1); + + // Then the old symbol cannot begin playback on the new page + assert.deepEqual(harness.audio, []); +}); + +test('user keeps the new-symbol audio queued when the previous-symbol cooldown completes', async (t) => { + // Given BTC has started native audio and owns the active cooldown + const harness = await createNotificationHarness(t); + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(500); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + + // When the user switches to ETH and its fill queues during the BTC cooldown + harness.navigate('/en/futures/ETHUSDT'); + await harness.notify(ordinaryNotificationOrder({ symbol: 'ETHUSDT', orderId: 43, clientOrderId: 'manual-43' })); + await harness.advance(500); + await harness.advance(1499); + + // Then the original cooldown has not played a second notification early + assert.equal(harness.audio.filter((event) => event.kind === 'load').length, 1); + + // When the original two-second cooldown completes + await harness.advance(1); + + // Then it consumes only the played BTC token and ETH still receives its own audio + assert.deepEqual(harness.audio.filter((event) => event.kind === 'load'), [ + { kind: 'load', pathname: '/en/futures/BTCUSDT' }, + { kind: 'load', pathname: '/en/futures/ETHUSDT' }, + ]); +}); + +test('user hears no second stale play when the first native play promise resolves after navigation', async (t) => { + // Given the first native audio play is pending on the BTC page + const harness = await createNotificationHarness(t, { firstPlayPending: true }); + await harness.notify(ordinaryNotificationOrder()); + await harness.advance(500); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play']); + + // When the route changes before that first play promise resolves + harness.navigate('/en/futures/ETHUSDT'); + await harness.settleFirstPlay(); + + // Then the promise continuation cannot replay the old symbol on the new page + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play']); +}); + +for (const driftModule of [39116, 55401]) { + test(`user keeps native sound and string payloads when captured module ${driftModule} drifts`, async (t) => { + // Given only one side of the native sound producer-provider contract can be patched + const harness = await createNotificationHarness(t, { driftModule, inspectSoundQueue: true }); + + // When a foreign-symbol ordinary fill reaches the native sound stream + await harness.notify(ordinaryNotificationOrder({ symbol: 'ETHUSDT' })); + await harness.advance(500); + + // Then sound stays native with its exact string payload while toast filtering remains independent + assert.deepEqual(harness.queueSnapshot(), ['42']); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); + assert.deepEqual(harness.toasts, []); + assert.deepEqual(harness.scope.snapshot().modules[driftModule], { + status: 'source_mismatch', reason: 'source_mismatch', attempts: 1, matches: 0, + }); + }); +} + +test('user keeps native notification behavior outside a recognized futures symbol route', async (t) => { + // Given the loaded host is on an unsupported non-futures route + const harness = await createNotificationHarness(t, { pathname: '/en/trade/BTC_USDT' }); + + // When an ordinary unrelated-symbol notification arrives + await harness.notify(ordinaryNotificationOrder({ symbol: 'ETHUSDT' })); + await harness.advance(500); + + // Then the scope cannot claim a futures symbol and keeps native presentation + assert.equal(harness.toasts.length, 1); + assert.deepEqual(harness.audio.map((event) => event.kind), ['load', 'play', 'play']); +}); + +test('user host stream boundary preserves single and batch payload identity and account separation', () => { + // Given independent native account subscriptions receive the SDK boundary + const streams = createNotificationStreams(); + const um = []; + const cm = []; + const unsubscribeUM = streams.getSDK({ isCM: false }).getUserOrderStream({ isPM2: false }).subscribe((value) => um.push(value)); + const unsubscribeCM = streams.getSDK({ isCM: true }).getUserOrderStream({ isPM2: true }).subscribe((value) => cm.push(value)); + const single = ordinaryNotificationOrder({ symbol: undefined }); + const batch = [ordinaryNotificationOrder({ symbol: 'BTCUSD_PERP' }), single]; + + // When the distinct account streams emit unchanged payload objects + streams.emit(single); + streams.emit(batch, true); + unsubscribeUM(); + streams.emit(batch); + unsubscribeCM(); + + // Then the boundary neither filters unknown fields nor crosses account channels + assert.equal(um.length, 1); + assert.equal(um[0], single); + assert.equal(cm.length, 1); + assert.equal(cm[0], batch); + assert.equal(streams.size(false), 0); + assert.equal(streams.size(true), 0); +}); + +test('user host toast adapter emits the original cached order instead of rebuilding it from the fill', () => { + // Given the actual native event emitter and cache adapter hold a foreign-symbol order + const dependencies = new Map([[94917, { _: (instance, Constructor) => assert.equal(instance instanceof Constructor, true) }]]); + const NativeEmitter = createNativeNotificationModules(dependencies)(22584).b; + const emitter = new NativeEmitter(); + const streams = createNotificationStreams(); + const host = attachNativeOrderEventHost({ streams, emitter }); + const received = []; + emitter.on('FILLED_NORMAL_ORDER', (order) => received.push(order)); + const original = ordinaryNotificationOrder({ symbol: 'ETHUSDT', status: 'NEW', originMarker: 'cache-entry' }); + streams.emit(original); + const cached = host.orders()[0]; + + // When the completion contains a different marker and the same native identity + streams.emit({ ...original, status: 'FILLED', originMarker: 'stream-update' }); + host.dispose(); + + // Then native subscribers receive the cached shape with its original NEW status + assert.equal(received.length, 1); + assert.equal(received[0], cached); + assert.equal(received[0].status, 'NEW'); + assert.equal(received[0].originMarker, 'cache-entry'); + assert.equal(received[0].symbol, 'ETHUSDT'); + assert.deepEqual(host.orders(), []); +}); + +test('user host SDK parser preserves the native raw websocket to normalized order contract', () => { + // Given the captured SDK order schema and its actual field parser use a controlled transport + const decoder = createNativeOrderDecoder('UM'); + const raw = { + fs: 'UM', E: 1800000000123, + o: { s: 'ETHUSDT', c: 'manual-42', i: 42, S: 'BUY', o: 'LIMIT', ot: 'LIMIT', x: 'TRADE', X: 'FILLED', p: '1.25', ap: '1.2', z: '3', q: '3', T: 1800000000000 }, + }; + + // When the native transform receives the original coalesced websocket envelope + const orders = decoder.decode({ coalscedMsg: [{ data: raw }] }); + + // Then native schema mapping retains identity and uses its actual numeric conversions + assert.equal(decoder.eventType, 'ORDER_TRADE_UPDATE'); + assert.equal(orders.length, 1); + assert.equal(Object.getPrototypeOf(orders[0]), null); + assert.deepEqual({ + symbol: orders[0].symbol, + clientOrderId: orders[0].clientOrderId, + orderId: orders[0].orderId, + type: orders[0].type, + orderType: orders[0].orderType, + origType: orders[0].origType, + operate: orders[0].operate, + status: orders[0].status, + price: orders[0].price, + avgPrice: orders[0].avgPrice, + executedQty: orders[0].executedQty, + origQty: orders[0].origQty, + updateTime: orders[0].updateTime, + }, { + symbol: 'ETHUSDT', clientOrderId: 'manual-42', orderId: 42, + type: 'LIMIT', orderType: 'LIMIT', origType: 'LIMIT', operate: 'TRADE', status: 'FILLED', + price: 1.25, avgPrice: 1.2, executedQty: 3, origQty: '3', updateTime: 1800000000000, + }); +}); + +test('user host SDK parser retains uncertain fields and applies its original account and update-time rules', () => { + // Given an account source contains unknown fields and one foreign account packet + const decoder = createNativeOrderDecoder('UM'); + const partial = { fs: 'UM', E: 99, o: { s: 'eth/usdt', o: 'UNRECOGNIZED', X: 'PARTIALLY_FILLED', x: 'TRADE', T: 1 } }; + const amendment = { fs: 'UM', E: 100, o: { s: 'BTCUSDT', o: 'LIMIT', X: 'NEW', x: 'AMENDMENT', T: 2 } }; + const otherAccount = { fs: 'CM', E: 101, o: { s: 'BTCUSD_PERP', X: 'FILLED' } }; + + // When actual native schema logic transforms the complete coalesced batch + const orders = decoder.decode({ coalscedMsg: [partial, amendment, otherAccount].map((data) => ({ data })) }); + + // Then only the native account filter applies and unknown metadata is neither repaired nor invented + assert.equal(orders.length, 2); + assert.equal(orders[0].symbol, 'eth/usdt'); + assert.equal(orders[0].type, 'UNRECOGNIZED'); + assert.equal(orders[0].clientOrderId, undefined); + assert.equal(orders[0].origType, undefined); + assert.equal(orders[0].updateTime, 99); + assert.equal(orders[1].updateTime, 100); +}); + +test('user filters the actual native normalized event produced from a foreign raw websocket order', async (t) => { + // Given the real SDK transform creates the native order shape from its raw envelope + const harness = await createNotificationHarness(t); + const decoder = createNativeOrderDecoder('UM'); + const [order] = decoder.decode({ coalscedMsg: [{ data: { + fs: 'UM', E: 1800000000123, + o: { s: 'ETHUSDT', c: 'manual-42', i: 42, S: 'BUY', o: 'LIMIT', ot: 'LIMIT', x: 'TRADE', X: 'FILLED', T: 1800000000000 }, + } }] }); + + // When that unmodified normalized order traverses the native cache, hooks and provider + await harness.notify(order); + await harness.advance(500); + + // Then filtering suppresses both final side effects without changing native schema fields + assert.deepEqual({ toasts: harness.toasts, audio: harness.audio }, { toasts: [], audio: [] }); + assert.equal(order.symbol, 'ETHUSDT'); + assert.equal(order.type, 'LIMIT'); + assert.equal(order.origType, 'LIMIT'); +}); diff --git a/test/fixtures/binance-order-notifications/native-factories.json b/test/fixtures/binance-order-notifications/native-factories.json new file mode 100644 index 0000000..145b6de --- /dev/null +++ b/test/fixtures/binance-order-notifications/native-factories.json @@ -0,0 +1,270 @@ +{ + "schema_version": 1, + "capture_date": "2026-10-07", + "factories": [ + { + "id": "39116", + "chunks": [ + "3314" + ], + "source": "39116(D,l,t){\"use strict\";t.d(l,{QZ:()=>p.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(A,500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length)i});var e=t(75510),a=t(41594),r=t.n(a),n=function(s,c,d){var y=Array.isArray(c)?c:[],p=typeof c==\"number\"?c:d||0,T=(0,a.useRef)(!1),S=(0,a.useRef)(),E=(0,a.useRef)(s);E.current=s;var A=(0,a.useRef)([]),O=(0,a.useCallback)(function(){S.current&&clearTimeout(S.current),S.current=void 0},[]),m=(0,a.useCallback)(function(){for(var g=arguments.length,u=new Array(g),o=0;oN,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$=\"trd-openOrder\",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)(\"open_order_status_toast\",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(h,30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,\"\");return\"\".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"CancelTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Canceled\")});x(Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"FillTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"PFTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Partially Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}", + "sha256": "8eaee3ce87f001f8530992b2ea4ce99ee632ef902f129e79c76d6b1268b4de1a", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/37511.5ad8e45f.js", + "sha256": "4450c0c462c214862282a421fe2d929fd40f22970e17e90814b29b2b21e04269", + "bytes": 124660, + "factory_utf16_range": [ + 13866, + 15298 + ] + } + }, + { + "id": "30877", + "chunks": [ + "24132" + ], + "source": "30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k=\"trd-openOrder\",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)(\"open_order_status_toast\",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(_,30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,\"\");return\"\".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"CancelTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Canceled\")});L(T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"FillTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"PFTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Partially Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}", + "sha256": "faec1b3eb9f86a4e7087128f8fd3b74a210581c9988a0041c909a848cfb0ae0f", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": "https://bin.bnbstatic.com/static/chunks/24132.91842e2a.js", + "evidence": "Observed scriptParsed URL and runtime factory source; complete chunk bytes were not captured." + } + }, + { + "id": "30877", + "chunks": [ + "57898" + ], + "source": "30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b=\"trd-openOrder\",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)(\"open_order_status_toast\",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(C,30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,\"\");return\"\".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"CancelTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Canceled\")});d(u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"FillTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"PFTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Partially Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}", + "sha256": "ab507602d8d3533b30239e7eadc4b2d53392dd18a2af5da4539f35feacdac2c6", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/57898.16437e66.js", + "sha256": "73221d1be841cc383f4b90b703713cdcf19455eadc78c64c40ca1692ff359f52", + "bytes": 102464, + "factory_utf16_range": [ + 78802, + 80233 + ] + } + }, + { + "id": "55401", + "chunks": [ + "29042" + ], + "source": "55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a){var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src=\"\".concat(x.K5,\"/static/spot-trade-ui/trade-sound.mp3\"),E(v),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)(\"div\",{children:[r,(0,c.jsx)(\"div\",{children:(0,c.jsx)(\"audio\",{ref:h,children:(0,c.jsx)(\"source\",{src:\"\"})})})]})}}", + "sha256": "98094af191e88c1637017da1ba05037fa1fb4c39a7028dc8e35d0aa1c39c2a3e", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/29042.3c0cf97b.js", + "sha256": "fc8acb6938b71aeb1054de9e970052b5bd5e84b53e9eb373378ef6e21c0b161e", + "bytes": 1076, + "factory_utf16_range": [ + 104, + 1071 + ] + } + }, + { + "id": "30877", + "chunks": [ + "57276" + ], + "source": "30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p=\"trd-openOrder\",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)(\"open_order_status_toast\",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(c,30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,\"\");return\"\".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"CancelTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Canceled\")});u(f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"FillTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"PFTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Partially Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}", + "sha256": "7465bd6eac312fcc597b231b916795bf0daaf51e01191f60e6a6c2b6adeabdba", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/57276.ae6ee5f0.js", + "sha256": "8bbc3de3d94e8e19dbd2946f22cb03b43d2cd8051c7da452c5c59ad67e8345bc", + "bytes": 4285, + "factory_utf16_range": [ + 2047, + 3478 + ] + } + }, + { + "id": "34122", + "chunks": [ + "57276" + ], + "source": "34122(Y,T,e){e.r(T),e.d(T,{OrderToastNotifyRegister:()=>N,OrderToastSwitch:()=>R});var a=e(31085),k=e(41594),P=e.n(k),l=e(71822),O=e(88478),C=e(48651),h=e(30877),M=e(34175),p=e(10157),R=function(){var o=(0,h.z)(),n=o.enableToast,c=o.toggleToast,i=(0,C.ud)().getI18n;return(0,a.jsxs)(\"div\",{className:p.XE,onClick:c,children:[(0,a.jsx)(\"div\",{className:p.IG,children:i(\"orderToastReminder\",{defaultValue:\"Toast Reminder\"})}),(0,a.jsx)(O.A,{size:\"small\",checked:n})]})},N=function(){var o=(0,h.F)();return(0,k.useEffect)(function(){var n=[l.J.on(\"NEW\",M.Zu),l.J.on(\"PARTIALLY_FILLED\",o.fillOrderPartNotify),l.J.on(\"CANCELED\",o.cancelOrderNotify),l.J.on(\"FILLED\",o.fillOrderNotify),l.J.on(\"FILLED_NORMAL_ORDER\",o.fillOrderNotify)];return function(){return n.forEach(function(c){return c()})}},[o]),null}}", + "sha256": "f036bb5b69da48fe84b75664e4e05079e1953baf0406dabdc60e07f3d716b492", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/57276.ae6ee5f0.js", + "sha256": "8bbc3de3d94e8e19dbd2946f22cb03b43d2cd8051c7da452c5c59ad67e8345bc", + "bytes": 4285, + "factory_utf16_range": [ + 3479, + 4280 + ] + } + }, + { + "id": "30877", + "chunks": [ + "76905" + ], + "source": "30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g=\"trd-openOrder\",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)(\"open_order_status_toast\",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(c,30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,\"\");return\"\".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"CancelTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Canceled\")});n(u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"FillTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"PFTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Partially Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}", + "sha256": "1f40d63c4f3f27799bd34fd93ece8686c6b276741d62f37d928cdcba858ec53f", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": "https://bin.bnbstatic.com/static/chunks/76905.2ae2c9b9.js", + "evidence": "Observed scriptParsed URL and runtime factory source; complete chunk bytes were not captured." + } + }, + { + "id": "30877", + "chunks": [ + "49426" + ], + "source": "30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o=\"trd-openOrder\",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)(\"open_order_status_toast\",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(M,30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,\"\");return\"\".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"CancelTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Canceled\")});_(c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"FillTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"PFTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Partially Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}", + "sha256": "a25ce74a214c9e2b80bc7141b20fe798c7fbc30bcb4f79c0a9c363b4cca7119f", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": "https://bin.bnbstatic.com/static/chunks/49426.57389f4e.js", + "evidence": "Observed scriptParsed URL and runtime factory source; complete chunk bytes were not captured." + } + }, + { + "id": "73335", + "chunks": [ + "40026" + ], + "source": "73335(ve,C,o){/**\n * @license React\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */var O=o(41594),S=Symbol.for(\"react.element\"),w=Symbol.for(\"react.fragment\"),Q=Object.prototype.hasOwnProperty,Y=O.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,H={key:!0,ref:!0,__self:!0,__source:!0};function oe(B,$,z){var D,P={},F=null,ae=null;z!==void 0&&(F=\"\"+z),$.key!==void 0&&(F=\"\"+$.key),$.ref!==void 0&&(ae=$.ref);for(D in $)Q.call($,D)&&!H.hasOwnProperty(D)&&(P[D]=$[D]);if(B&&B.defaultProps)for(D in $=B.defaultProps,$)P[D]===void 0&&(P[D]=$[D]);return{$$typeof:S,type:B,key:F,ref:ae,props:P,_owner:Y.current}}C.Fragment=w,C.jsx=oe,C.jsxs=oe}", + "sha256": "45e3ee5e33406e46329e7f641af069ba469481d4a3d003ebae8af2a46df77e94", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": null, + "evidence": "Runtime factory source and registration chunk ID only; complete chunk bytes and script URL were not captured." + } + }, + { + "id": "22584", + "chunks": [ + "67656" + ], + "source": "22584(_,y,e){\"use strict\";e.d(y,{b:()=>i});var t=e(94917),i=function r(){\"use strict\";var a=this;(0,t._)(this,r),this.cbs={},this.on=function(s,n){var o=a;return a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].push(n),function(){return o.off(s,n)}},this.off=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s]=a.cbs[s].filter(function(o){return o!==n})},this.emit=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].forEach(function(o){return typeof o==\"function\"&&o(n)})}}}", + "sha256": "dbb8a7a3bddd76408b5382a0f20a6ee7ad45d09ad61ce2168c3de799aafd2f9b", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": null, + "evidence": "Runtime factory source and registration chunk ID only; complete chunk bytes and script URL were not captured." + } + }, + { + "id": "5558", + "chunks": [ + "32536" + ], + "source": "5558(ee,K,t){t.d(K,{$Q:()=>T,Ai:()=>S,CC:()=>R,H9:()=>h,Ub:()=>O,cg:()=>A,qS:()=>F});var P=t(61489),n=t(37230),o=t(53548);const S={symbol:\"s\",clientOrderId:\"c\",side:\"S\",orderType:\"o\",timeInForce:\"f\",quantity:n.X9.number(\"q\"),price:n.X9.number(\"p\"),stopPrice:n.X9.number(\"P\"),icebergQuantity:n.X9.number(\"F\"),orderListId:\"g\",originalClientOrderId:\"C\",executionType:\"x\",orderStatus:\"X\",orderRejectReason:\"r\",orderId:\"i\",LastExecutedQuantity:n.X9.number(\"l\"),cumulativeFilledQuantity:n.X9.number(\"z\"),lastPrice:n.X9.number(\"L\"),commissionAmount:n.X9.number(\"n\"),commissionAsset:\"N\",transactionTime:\"T\",tradeId:\"t\",isOnOrderBook:\"w\",isMaker:\"m\",orderCreationTime:\"O\",cumulativeQuoteQuantity:n.X9.number(\"Z\"),lastQuoteQuantity:n.X9.number(\"Y\"),quoteOrderQty:n.X9.number(\"Q\"),activatePrice:n.X9.number(\"AP\"),callbackRate:n.X9.number(\"cr\"),trailingDelta:\"d\",trailingTime:\"D\",workingTime:\"W\",isSor:\"uS\",pegPriceType:\"gP\",pegOffsetType:\"gOT\",pegOffsetValue:\"gOV\",peggedPrice:\"gp\"},T=Object.keys(S),I={symbol:\"s\",insertTime:\"T\",type:\"o\",side:\"S\",price:n.X9.number(\"p\"),avgPrice:n.X9.number(\"ap\"),executedPrice:n.X9.number(\"ap\"),executedQty:n.X9.number(\"z\"),origQty:\"q\",stopPrice:n.X9.number(\"sp\"),operate:\"x\",status:\"X\",orderId:\"i\",clientOrderId:\"c\",orderType:\"o\",origType:\"ot\",timeInForce:\"f\",goodTillDate:n.X9.number(\"gtd\"),bidNotional:n.X9.number(\"b\"),askNotional:n.X9.number(\"a\"),workingType:\"wt\",positionSide:\"ps\",reduceOnly:\"R\",strategyId:\"si\",strategySubId:\"ss\",strategyType:\"st\",closePosition:\"cp\",activatePrice:n.X9.number(\"AP\"),callbackRate:n.X9.number(\"cr\"),priceRate:n.X9.number(\"cr\"),priceMatch:\"pm\",updateTime:\"T\"},l=Object.keys(I),f={algoId:\"aid\",clientAlgoId:\"caid\",algoType:\"at\",orderType:\"o\",symbol:\"s\",side:\"S\",positionSide:\"ps\",timeInForce:\"f\",quantity:\"q\",algoStatus:\"X\",actualOrderId:\"ai\",actualPrice:\"ap\",actualQty:\"aq\",actualType:\"act\",triggerPrice:\"tp\",price:\"p\",icebergQuantity:\"iq\",tpTriggerPrice:\"tptp\",tpPrice:\"tpp\",tpOrderType:\"tpot\",slTriggerPrice:\"sltp\",slPrice:\"slp\",slOrderType:\"slot\",selfTradePreventionMode:\"V\",workingType:\"wt\",priceMatch:\"pm\",closePosition:\"cp\",priceProtect:\"pP\",reduceOnly:\"R\",activatePrice:\"AP\",callbackRate:\"cr\",goodTillDate:n.X9.number(\"gtd\"),triggerTime:n.X9.number(\"tt\"),updateTime:n.X9.now()},y=Object.keys(f),g={symbol:\"s\",strategyId:\"si\",newClientStrategyId:\"c\",side:\"S\",strategyType:\"st\",timeInForce:\"f\",origQty:\"q\",price:\"p\",stopPrice:\"sp\",strategyStatus:\"os\",bookTime:\"T\",reduceOnly:\"R\",workingType:\"wt\",positionSide:\"ps\",closePosition:\"cp\",activatePrice:\"AP\",priceRate:n.X9.number(\"cr\"),priceMatch:\"pm\",orderId:\"i\",parentStrategyId:\"pi\",parentSubId:\"PS\",parentStrategyType:\"pt\",goodTillDate:n.X9.number(\"gtd\")},r=Object.keys(g),d={symbol:\"s\",strategyClientOrderId:\"c\",strategyType:\"st\",strategyId:\"si\",takeProfitPrice:\"tpp\",stopLossPrice:\"slp\",takeProfitTriggerPrice:\"tptp\",stopLossTriggerPrice:\"sltp\"},i=Object.keys(d);function A(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"executionReport\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>j((0,n.Li)(V,S,T)))},_,W);return(0,o.G)(X)}function R(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"ORDER_TRADE_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{if(V.fs&&V.fs!==m.PMFuturesSource)return;const c=(0,n.Li)(V.o,I,l);return(V.o.X===\"PARTIALLY_FILLED\"||V.o.x===\"AMENDMENT\")&&(c.updateTime=V.E),j(c)})},_,W);return(0,o.G)(X)}function F(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"ALGO_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{const c=\"ao\"in V?V.ao:V.o;return j((0,n.Li)(c,f,y))})},_,W);return(0,o.G)(X)}function O(m){const _=(0,P.s)(m,{type:m.type,eventType:\"CONDITIONAL_ORDER_TRADE_UPDATE\",transform:W=>X=>W.coalscedMsg.forEach(({data:Q})=>{if(!(Q.fs&&Q.fs!==m.PMFuturesSource))return X((0,n.Li)(Q.so,g,r))})});return(0,o.G)(_)}function h(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"OTOCO_ORDER_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{j((0,n.Li)(V.su,d,i))})},_,W);return(0,o.G)(X)}}", + "sha256": "352d145ced1d19136316a6d5768c7b9440f3ab66986db37d5706ff84a312f884", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": "https://bin.bnbstatic.com/static/chunks/32536.f022d7ba.js", + "evidence": "Observed scriptParsed URL and runtime factory source; complete chunk bytes were not captured." + } + }, + { + "id": "70020", + "chunks": [ + "3314" + ], + "source": "70020(D,l,t){\"use strict\";t.d(l,{Q:()=>d});var e=t(41594),a=t.n(e),r=t(80065),n=t(96636),i=t(51471),s=\"open-order-notification-sound-open\",c=(0,r.A)(function(y){return{enableNotification:!1,setNotification:function(p){return y({enableNotification:p})}}}),d=function(){var y=(0,e.useState)(!1),p=y[0],T=y[1],S=(0,i.zr)(s,!1),E=S.data,A=S.setData,O=S.hasInitialized,m=c(),g=m.enableNotification,u=m.setNotification,o=(0,n.Z)().isEUFuturesUrl;(0,e.useEffect)(function(){p||O&&(u(E),T(!0))},[E,p,O,u]);var f=(0,e.useCallback)(function(){u(!g),A(!g)},[g,A,u]);return{initialized:p,showNotification:!o,enableNotification:o?!1:g,toggleNotification:f}}}", + "sha256": "9e247d3e16a783e1c6f2b2c529d64241db8ec3b52afe5d7de1618aae36467963", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "complete_loaded_source", + "url": "https://bin.bnbstatic.com/static/chunks/3314.51597d7f.js", + "sha256": "9081346db36168e132d8cb36131c8a02703e1aa8b296ea8e87e22aad13d478d1", + "bytes": 103437, + "factory_utf16_range": [ + 77204, + 77849 + ] + } + }, + { + "id": "4189", + "chunks": [ + "14105" + ], + "source": "4189(U,P,e){e.d(P,{zH:()=>D,nT:()=>h,u7:()=>Q,$t:()=>u,Qj:()=>T});var s=e(41594),l=e(87017),y=e(48187),m=e(57861),L=e(41466);const M=async g=>{let f;try{f=await(0,m.post)(`/bapi/futures/v1/private/future/order/open-orders?${(0,L.E2)(g||{})}`,{})}catch{f={success:!1}}return f};var S=e(43335),R=e(26860),N=e(84266),I=e(16921),C=e(90291),A=e(47738),a=e(95541),F=e(71822);const d=(g,f,i)=>{const{isCM:b}=i,p=[...g];return f.forEach(O=>{const{symbol:k,status:B,strategyType:G=null,clientOrderId:K}=O,z={...O,strategyType:G},x=p.findIndex(V=>V.clientOrderId===K&&V.symbol===k);switch(B){case\"NEW\":F.J.emit(\"NEW\",{isCM:b,clientOrderId:K}),x!==-1?p[x]=z:p.unshift(z);break;case\"PARTIALLY_FILLED\":x!==-1&&(O.operate!==\"AMENDMENT\"&&F.J.emit(\"PARTIALLY_FILLED\",p[x]),p[x]=z);break;case\"CANCELED\":case\"EXPIRED\":case\"EXPIRED_IN_MATCH\":x!==-1&&(F.J.emit(\"CANCELED\",p[x]),p.splice(x,1));break;case\"FILLED\":x!==-1&&(F.J.emit(\"FILLED_NORMAL_ORDER\",p[x]),p.splice(x,1));break;default:break}}),p},v=({isPM2:g,isCM:f,stream:i,queryClient:b,copyTradingPayload:p})=>{const O=p||{},k=f?a.CX:(0,a.kc)({copyTradingPayload:O}),B=K=>{b.setQueryData(S.Bz.OPEN_ORDERS(f,O),z=>({success:!0,data:d(z?.data||[],K,{isCM:f})}))};k.addWorker(B);const G=i.getUserOrderStream({isPM2:g,params:O}).subscribe(K=>{k.push(K)});return()=>{G(),k.removeWorker(B)}},o=(0,A.Y)({queryKey:S.Bz.OPEN_ORDERS,subscribeToStreamFn:v}),T=async({isCM:g,copyTradingPayload:f})=>g?(0,y.mp)():M(f),u=({enabled:g,isCM:f,isPM2:i,getSDK:b,queryClient:p,copyTradingPayload:O})=>{g&&o({getSDK:b,queryClient:p,isCM:f,isPM2:i,copyTradingPayload:O})},D=(g,{disableWs:f,...i}={})=>{const b=(0,R.n)(),{isExistFutureAccount:p}=(0,N.Py)(),O=(0,I.by)(),k=!!(b&&p),B=(0,I.Gw)(),G=(0,l.useQueryClient)(),K=(0,C.OE)();return(0,s.useEffect)(function(){u({enabled:k&&!f,isCM:g,isPM2:O,getSDK:K,queryClient:G,copyTradingPayload:B})},[k,g,O,K,G,f,B]),(0,l.useQuery)(S.Bz.OPEN_ORDERS(g,B),()=>T({isCM:g,copyTradingPayload:B}),{enabled:k,cacheTime:1/0,staleTime:1/0,notifyOnChangeProps:[\"data\"],...i})},Q=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>{f.invalidateQueries(S.Bz.OPEN_ORDERS(g,i))},[i,g,f])},h=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>f.getQueryData(S.Bz.OPEN_ORDERS(g,i))?.data?.length||0,[i,g,f])}}", + "sha256": "1a9a1cfa869a72786a4b53fb5f800f5bd81fbd67ba22ad707404ee1be7b301cf", + "evidence": "Runtime Function.prototype.toString source captured read-only on 2026-10-07.", + "bundle": { + "availability": "unavailable", + "url": "https://bin.bnbstatic.com/static/chunks/14105.10a31c5d.js", + "evidence": "Observed scriptParsed URL and runtime factory source; complete chunk bytes were not captured." + } + }, + { + "id": "37230", + "chunks": [ + "32536" + ], + "source": "37230(ee,K,t){t.d(K,{Li:()=>S,Rr:()=>n,X9:()=>o});const P={isolated:!0,cross:!1};function n(T){const I=T.split(\".\");return l=>{for(let f=0;fNumber(I(l))},text(T){const I=n(T);return l=>I(l)},isolatedVal(T){const I=n(T);return l=>P[I(l)]},now(){return T=>Date.now()}};function S(T,I,l){const f=Object.create(null);for(let y=0;yp.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(A,500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status===\"FILLED\"||P.status===\"PARTIALLY_FILLED\"||P.status===\"EXPIRED\")&&u(String(P.orderId))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length)i});var e=t(75510),a=t(41594),r=t.n(a),n=function(s,c,d){var y=Array.isArray(c)?c:[],p=typeof c==\"number\"?c:d||0,T=(0,a.useRef)(!1),S=(0,a.useRef)(),E=(0,a.useRef)(s);E.current=s;var A=(0,a.useRef)([]),O=(0,a.useCallback)(function(){S.current&&clearTimeout(S.current),S.current=void 0},[]),m=(0,a.useCallback)(function(){for(var g=arguments.length,u=new Array(g),o=0;oN,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$=\"trd-openOrder\",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)(\"open_order_status_toast\",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(h,30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,\"\");return\"\".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"CancelTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Canceled\")});x(Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"FillTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!(l.type===\"LIQUIDATION\"||!R)){var D=A(l),Y=c(\"\".concat(D,\"PFTitle\"),{defaultValue:\"\".concat(l.type,\" \").concat(l.side,\" Order Partially Filled\")});x(Y,{variant:\"success\"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}" + }, + { + "id": "30877", + "chunks": [ + "24132" + ], + "source": "30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k=\"trd-openOrder\",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)(\"open_order_status_toast\",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(_,30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,\"\");return\"\".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"CancelTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Canceled\")});L(T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"FillTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!(i.type===\"LIQUIDATION\"||!F)){var S=C(i),T=g(\"\".concat(S,\"PFTitle\"),{defaultValue:\"\".concat(i.type,\" \").concat(i.side,\" Order Partially Filled\")});L(T,{variant:\"success\"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}" + }, + { + "id": "30877", + "chunks": [ + "57898" + ], + "source": "30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b=\"trd-openOrder\",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)(\"open_order_status_toast\",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(C,30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,\"\");return\"\".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"CancelTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Canceled\")});d(u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"FillTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!(a.type===\"LIQUIDATION\"||!f)){var o=k(a),u=y(\"\".concat(o,\"PFTitle\"),{defaultValue:\"\".concat(a.type,\" \").concat(a.side,\" Order Partially Filled\")});d(u,{variant:\"success\"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}" + }, + { + "id": "55401", + "chunks": [ + "29042" + ], + "source": "55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a){var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src=\"\".concat(x.K5,\"/static/spot-trade-ui/trade-sound.mp3\"),E(v),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)(\"div\",{children:[r,(0,c.jsx)(\"div\",{children:(0,c.jsx)(\"audio\",{ref:h,children:(0,c.jsx)(\"source\",{src:\"\"})})})]})}}" + }, + { + "id": "30877", + "chunks": [ + "57276" + ], + "source": "30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p=\"trd-openOrder\",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)(\"open_order_status_toast\",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(c,30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,\"\");return\"\".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"CancelTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Canceled\")});u(f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"FillTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!(r.type===\"LIQUIDATION\"||!i)){var d=I(r),f=n(\"\".concat(d,\"PFTitle\"),{defaultValue:\"\".concat(r.type,\" \").concat(r.side,\" Order Partially Filled\")});u(f,{variant:\"success\"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}" + }, + { + "id": "34122", + "chunks": [ + "57276" + ], + "source": "34122(Y,T,e){e.r(T),e.d(T,{OrderToastNotifyRegister:()=>N,OrderToastSwitch:()=>R});var a=e(31085),k=e(41594),P=e.n(k),l=e(71822),O=e(88478),C=e(48651),h=e(30877),M=e(34175),p=e(10157),R=function(){var o=(0,h.z)(),n=o.enableToast,c=o.toggleToast,i=(0,C.ud)().getI18n;return(0,a.jsxs)(\"div\",{className:p.XE,onClick:c,children:[(0,a.jsx)(\"div\",{className:p.IG,children:i(\"orderToastReminder\",{defaultValue:\"Toast Reminder\"})}),(0,a.jsx)(O.A,{size:\"small\",checked:n})]})},N=function(){var o=(0,h.F)();return(0,k.useEffect)(function(){var n=[l.J.on(\"NEW\",M.Zu),l.J.on(\"PARTIALLY_FILLED\",o.fillOrderPartNotify),l.J.on(\"CANCELED\",o.cancelOrderNotify),l.J.on(\"FILLED\",o.fillOrderNotify),l.J.on(\"FILLED_NORMAL_ORDER\",o.fillOrderNotify)];return function(){return n.forEach(function(c){return c()})}},[o]),null}}" + }, + { + "id": "30877", + "chunks": [ + "76905" + ], + "source": "30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g=\"trd-openOrder\",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)(\"open_order_status_toast\",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(c,30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,\"\");return\"\".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"CancelTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Canceled\")});n(u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"FillTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!(t.type===\"LIQUIDATION\"||!o)){var l=i(t),u=s(\"\".concat(l,\"PFTitle\"),{defaultValue:\"\".concat(t.type,\" \").concat(t.side,\" Order Partially Filled\")});n(u,{variant:\"success\"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}" + }, + { + "id": "30877", + "chunks": [ + "49426" + ], + "source": "30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o=\"trd-openOrder\",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)(\"open_order_status_toast\",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(M,30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,\"\");return\"\".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"CancelTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Canceled\")});_(c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"FillTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!(n.type===\"LIQUIDATION\"||!P)){var g=s(n),c=S(\"\".concat(g,\"PFTitle\"),{defaultValue:\"\".concat(n.type,\" \").concat(n.side,\" Order Partially Filled\")});_(c,{variant:\"success\"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}" + }, + { + "id": "73335", + "chunks": [ + "40026" + ], + "source": "73335(ve,C,o){/**\n * @license React\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */var O=o(41594),S=Symbol.for(\"react.element\"),w=Symbol.for(\"react.fragment\"),Q=Object.prototype.hasOwnProperty,Y=O.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,H={key:!0,ref:!0,__self:!0,__source:!0};function oe(B,$,z){var D,P={},F=null,ae=null;z!==void 0&&(F=\"\"+z),$.key!==void 0&&(F=\"\"+$.key),$.ref!==void 0&&(ae=$.ref);for(D in $)Q.call($,D)&&!H.hasOwnProperty(D)&&(P[D]=$[D]);if(B&&B.defaultProps)for(D in $=B.defaultProps,$)P[D]===void 0&&(P[D]=$[D]);return{$$typeof:S,type:B,key:F,ref:ae,props:P,_owner:Y.current}}C.Fragment=w,C.jsx=oe,C.jsxs=oe}" + }, + { + "id": "22584", + "chunks": [ + "67656" + ], + "source": "22584(_,y,e){\"use strict\";e.d(y,{b:()=>i});var t=e(94917),i=function r(){\"use strict\";var a=this;(0,t._)(this,r),this.cbs={},this.on=function(s,n){var o=a;return a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].push(n),function(){return o.off(s,n)}},this.off=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s]=a.cbs[s].filter(function(o){return o!==n})},this.emit=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].forEach(function(o){return typeof o==\"function\"&&o(n)})}}}" + }, + { + "id": "5558", + "chunks": [ + "32536" + ], + "source": "5558(ee,K,t){t.d(K,{$Q:()=>T,Ai:()=>S,CC:()=>R,H9:()=>h,Ub:()=>O,cg:()=>A,qS:()=>F});var P=t(61489),n=t(37230),o=t(53548);const S={symbol:\"s\",clientOrderId:\"c\",side:\"S\",orderType:\"o\",timeInForce:\"f\",quantity:n.X9.number(\"q\"),price:n.X9.number(\"p\"),stopPrice:n.X9.number(\"P\"),icebergQuantity:n.X9.number(\"F\"),orderListId:\"g\",originalClientOrderId:\"C\",executionType:\"x\",orderStatus:\"X\",orderRejectReason:\"r\",orderId:\"i\",LastExecutedQuantity:n.X9.number(\"l\"),cumulativeFilledQuantity:n.X9.number(\"z\"),lastPrice:n.X9.number(\"L\"),commissionAmount:n.X9.number(\"n\"),commissionAsset:\"N\",transactionTime:\"T\",tradeId:\"t\",isOnOrderBook:\"w\",isMaker:\"m\",orderCreationTime:\"O\",cumulativeQuoteQuantity:n.X9.number(\"Z\"),lastQuoteQuantity:n.X9.number(\"Y\"),quoteOrderQty:n.X9.number(\"Q\"),activatePrice:n.X9.number(\"AP\"),callbackRate:n.X9.number(\"cr\"),trailingDelta:\"d\",trailingTime:\"D\",workingTime:\"W\",isSor:\"uS\",pegPriceType:\"gP\",pegOffsetType:\"gOT\",pegOffsetValue:\"gOV\",peggedPrice:\"gp\"},T=Object.keys(S),I={symbol:\"s\",insertTime:\"T\",type:\"o\",side:\"S\",price:n.X9.number(\"p\"),avgPrice:n.X9.number(\"ap\"),executedPrice:n.X9.number(\"ap\"),executedQty:n.X9.number(\"z\"),origQty:\"q\",stopPrice:n.X9.number(\"sp\"),operate:\"x\",status:\"X\",orderId:\"i\",clientOrderId:\"c\",orderType:\"o\",origType:\"ot\",timeInForce:\"f\",goodTillDate:n.X9.number(\"gtd\"),bidNotional:n.X9.number(\"b\"),askNotional:n.X9.number(\"a\"),workingType:\"wt\",positionSide:\"ps\",reduceOnly:\"R\",strategyId:\"si\",strategySubId:\"ss\",strategyType:\"st\",closePosition:\"cp\",activatePrice:n.X9.number(\"AP\"),callbackRate:n.X9.number(\"cr\"),priceRate:n.X9.number(\"cr\"),priceMatch:\"pm\",updateTime:\"T\"},l=Object.keys(I),f={algoId:\"aid\",clientAlgoId:\"caid\",algoType:\"at\",orderType:\"o\",symbol:\"s\",side:\"S\",positionSide:\"ps\",timeInForce:\"f\",quantity:\"q\",algoStatus:\"X\",actualOrderId:\"ai\",actualPrice:\"ap\",actualQty:\"aq\",actualType:\"act\",triggerPrice:\"tp\",price:\"p\",icebergQuantity:\"iq\",tpTriggerPrice:\"tptp\",tpPrice:\"tpp\",tpOrderType:\"tpot\",slTriggerPrice:\"sltp\",slPrice:\"slp\",slOrderType:\"slot\",selfTradePreventionMode:\"V\",workingType:\"wt\",priceMatch:\"pm\",closePosition:\"cp\",priceProtect:\"pP\",reduceOnly:\"R\",activatePrice:\"AP\",callbackRate:\"cr\",goodTillDate:n.X9.number(\"gtd\"),triggerTime:n.X9.number(\"tt\"),updateTime:n.X9.now()},y=Object.keys(f),g={symbol:\"s\",strategyId:\"si\",newClientStrategyId:\"c\",side:\"S\",strategyType:\"st\",timeInForce:\"f\",origQty:\"q\",price:\"p\",stopPrice:\"sp\",strategyStatus:\"os\",bookTime:\"T\",reduceOnly:\"R\",workingType:\"wt\",positionSide:\"ps\",closePosition:\"cp\",activatePrice:\"AP\",priceRate:n.X9.number(\"cr\"),priceMatch:\"pm\",orderId:\"i\",parentStrategyId:\"pi\",parentSubId:\"PS\",parentStrategyType:\"pt\",goodTillDate:n.X9.number(\"gtd\")},r=Object.keys(g),d={symbol:\"s\",strategyClientOrderId:\"c\",strategyType:\"st\",strategyId:\"si\",takeProfitPrice:\"tpp\",stopLossPrice:\"slp\",takeProfitTriggerPrice:\"tptp\",stopLossTriggerPrice:\"sltp\"},i=Object.keys(d);function A(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"executionReport\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>j((0,n.Li)(V,S,T)))},_,W);return(0,o.G)(X)}function R(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"ORDER_TRADE_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{if(V.fs&&V.fs!==m.PMFuturesSource)return;const c=(0,n.Li)(V.o,I,l);return(V.o.X===\"PARTIALLY_FILLED\"||V.o.x===\"AMENDMENT\")&&(c.updateTime=V.E),j(c)})},_,W);return(0,o.G)(X)}function F(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"ALGO_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{const c=\"ao\"in V?V.ao:V.o;return j((0,n.Li)(c,f,y))})},_,W);return(0,o.G)(X)}function O(m){const _=(0,P.s)(m,{type:m.type,eventType:\"CONDITIONAL_ORDER_TRADE_UPDATE\",transform:W=>X=>W.coalscedMsg.forEach(({data:Q})=>{if(!(Q.fs&&Q.fs!==m.PMFuturesSource))return X((0,n.Li)(Q.so,g,r))})});return(0,o.G)(_)}function h(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:\"OTOCO_ORDER_UPDATE\",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{j((0,n.Li)(V.su,d,i))})},_,W);return(0,o.G)(X)}}" + }, + { + "id": "70020", + "chunks": [ + "3314" + ], + "source": "70020(D,l,t){\"use strict\";t.d(l,{Q:()=>d});var e=t(41594),a=t.n(e),r=t(80065),n=t(96636),i=t(51471),s=\"open-order-notification-sound-open\",c=(0,r.A)(function(y){return{enableNotification:!1,setNotification:function(p){return y({enableNotification:p})}}}),d=function(){var y=(0,e.useState)(!1),p=y[0],T=y[1],S=(0,i.zr)(s,!1),E=S.data,A=S.setData,O=S.hasInitialized,m=c(),g=m.enableNotification,u=m.setNotification,o=(0,n.Z)().isEUFuturesUrl;(0,e.useEffect)(function(){p||O&&(u(E),T(!0))},[E,p,O,u]);var f=(0,e.useCallback)(function(){u(!g),A(!g)},[g,A,u]);return{initialized:p,showNotification:!o,enableNotification:o?!1:g,toggleNotification:f}}}" + }, + { + "id": "4189", + "chunks": [ + "14105" + ], + "source": "4189(U,P,e){e.d(P,{zH:()=>D,nT:()=>h,u7:()=>Q,$t:()=>u,Qj:()=>T});var s=e(41594),l=e(87017),y=e(48187),m=e(57861),L=e(41466);const M=async g=>{let f;try{f=await(0,m.post)(`/bapi/futures/v1/private/future/order/open-orders?${(0,L.E2)(g||{})}`,{})}catch{f={success:!1}}return f};var S=e(43335),R=e(26860),N=e(84266),I=e(16921),C=e(90291),A=e(47738),a=e(95541),F=e(71822);const d=(g,f,i)=>{const{isCM:b}=i,p=[...g];return f.forEach(O=>{const{symbol:k,status:B,strategyType:G=null,clientOrderId:K}=O,z={...O,strategyType:G},x=p.findIndex(V=>V.clientOrderId===K&&V.symbol===k);switch(B){case\"NEW\":F.J.emit(\"NEW\",{isCM:b,clientOrderId:K}),x!==-1?p[x]=z:p.unshift(z);break;case\"PARTIALLY_FILLED\":x!==-1&&(O.operate!==\"AMENDMENT\"&&F.J.emit(\"PARTIALLY_FILLED\",p[x]),p[x]=z);break;case\"CANCELED\":case\"EXPIRED\":case\"EXPIRED_IN_MATCH\":x!==-1&&(F.J.emit(\"CANCELED\",p[x]),p.splice(x,1));break;case\"FILLED\":x!==-1&&(F.J.emit(\"FILLED_NORMAL_ORDER\",p[x]),p.splice(x,1));break;default:break}}),p},v=({isPM2:g,isCM:f,stream:i,queryClient:b,copyTradingPayload:p})=>{const O=p||{},k=f?a.CX:(0,a.kc)({copyTradingPayload:O}),B=K=>{b.setQueryData(S.Bz.OPEN_ORDERS(f,O),z=>({success:!0,data:d(z?.data||[],K,{isCM:f})}))};k.addWorker(B);const G=i.getUserOrderStream({isPM2:g,params:O}).subscribe(K=>{k.push(K)});return()=>{G(),k.removeWorker(B)}},o=(0,A.Y)({queryKey:S.Bz.OPEN_ORDERS,subscribeToStreamFn:v}),T=async({isCM:g,copyTradingPayload:f})=>g?(0,y.mp)():M(f),u=({enabled:g,isCM:f,isPM2:i,getSDK:b,queryClient:p,copyTradingPayload:O})=>{g&&o({getSDK:b,queryClient:p,isCM:f,isPM2:i,copyTradingPayload:O})},D=(g,{disableWs:f,...i}={})=>{const b=(0,R.n)(),{isExistFutureAccount:p}=(0,N.Py)(),O=(0,I.by)(),k=!!(b&&p),B=(0,I.Gw)(),G=(0,l.useQueryClient)(),K=(0,C.OE)();return(0,s.useEffect)(function(){u({enabled:k&&!f,isCM:g,isPM2:O,getSDK:K,queryClient:G,copyTradingPayload:B})},[k,g,O,K,G,f,B]),(0,l.useQuery)(S.Bz.OPEN_ORDERS(g,B),()=>T({isCM:g,copyTradingPayload:B}),{enabled:k,cacheTime:1/0,staleTime:1/0,notifyOnChangeProps:[\"data\"],...i})},Q=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>{f.invalidateQueries(S.Bz.OPEN_ORDERS(g,i))},[i,g,f])},h=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>f.getQueryData(S.Bz.OPEN_ORDERS(g,i))?.data?.length||0,[i,g,f])}}" + }, + { + "id": "37230", + "chunks": [ + "32536" + ], + "source": "37230(ee,K,t){t.d(K,{Li:()=>S,Rr:()=>n,X9:()=>o});const P={isolated:!0,cross:!1};function n(T){const I=T.split(\".\");return l=>{for(let f=0;fNumber(I(l))},text(T){const I=n(T);return l=>I(l)},isolatedVal(T){const I=n(T);return l=>P[I(l)]},now(){return T=>Date.now()}};function S(T,I,l){const f=Object.create(null);for(let y=0;yp.Q,$$:()=>n,E$:()=>S});var e=t(41594),a=2e3,r=2,n=function(){var E=(0,e.useRef)(null),A=function(O){E.current=setTimeout(function(){O(),E.current&&clearTimeout(E.current),E.current=null},a)};return(0,e.useMemo)(function(){return{interval:E,intervalFunc:A}},[])},i=t(75510),s=t(16921),c=t(72363),d=t(79515),y=t(40477),p=t(70020),T=function(E,A){var O=(0,d.nH)(),m=(0,d.Py)().isExistFutureAccount,g=(0,y.A)(A,500),u=g.run,o=(0,d.ON)().isPM2,f=(0,s.Gw)();(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!0});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&u(String(P.orderId))})})},[u,E,m,O,o,f]),(0,e.useEffect)(function(){if(!O||!m||!E)return function(){return null};var _=(0,c.Ri)({isCM:!1});return _.getUserOrderStream({isPM2:o,params:f}).subscribe(function(v){var L=Array.isArray(v)?(0,i._)(v):[v];L.forEach(function(P){(P.status==="FILLED"||P.status==="PARTIALLY_FILLED"||P.status==="EXPIRED")&&u(String(P.orderId))})})},[u,E,m,O,o,f])},S=function(){var E=(0,e.useState)(new Date),A=E[0],O=E[1],m=(0,e.useRef)([]),g=(0,p.Q)(),u=g.showNotification,o=g.enableNotification,f=(0,e.useCallback)(function(){var v;(v=m)===null||v===void 0||v.current.shift(),O(new Date)},[]),_=(0,e.useCallback)(function(v){var L;if(u&&((L=m)===null||L===void 0?void 0:L.current.length)i});var e=t(75510),a=t(41594),r=t.n(a),n=function(s,c,d){var y=Array.isArray(c)?c:[],p=typeof c=="number"?c:d||0,T=(0,a.useRef)(!1),S=(0,a.useRef)(),E=(0,a.useRef)(s);E.current=s;var A=(0,a.useRef)([]),O=(0,a.useCallback)(function(){S.current&&clearTimeout(S.current),S.current=void 0},[]),m=(0,a.useCallback)(function(){for(var g=arguments.length,u=new Array(g),o=0;oN,z:()=>o});var r=e(41594),a=e.n(r),T=e(61523),p=e(92873),P=e(64041),b=e(17409),S=e(51471),E=e(40477),$="trd-openOrder",G=function(c){return(0,p.o)($,c)},o=function(){var c=(0,S.zr)("open_order_status_toast",!0),h=c.data,R=c.setData,K=c.hasInitialized,x=(0,r.useCallback)(function(){R(!h)},[h,R]);return{enableToast:K?h:!1,toggleToast:x}},N=function(){var c=G().getI18n,h=(0,P.h)().enqueueNotification,R=o().enableToast,K=(0,E.A)(h,30),x=K.run,i=(0,T.d4)(function(l){return l.setting.layout}),d=(0,r.useMemo)(function(){return i===b.a0},[i]),A=(0,r.useCallback)(function(l){var D=l.side.toUpperCase(),Y=l.type.replace(/[-_]/g,"");return"".concat(D).concat(Y)},[]),g=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"CancelTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Canceled")});x(Y)}},[A,c,x,R]),C=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"FillTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Filled")});x(Y,{variant:"success"})}},[A,c,x,R]),I=(0,r.useCallback)(function(l){if(!(l.type==="LIQUIDATION"||!R)){var D=A(l),Y=c("".concat(D,"PFTitle"),{defaultValue:"".concat(l.type," ").concat(l.side," Order Partially Filled")});x(Y,{variant:"success"})}},[A,c,x,R]);return(0,r.useMemo)(function(){return{cancelOrderNotify:g,fillOrderNotify:C,fillOrderPartNotify:I}},[g,C,I])}}}[30877] }, + { id: "30877", chunkId: "24132", factory: {30877(V,b,e){e.d(b,{F:()=>m,z:()=>I});var r=e(41594),A=e.n(r),s=e(61523),f=e(92873),v=e(64041),o=e(17409),l=e(51471),P=e(40477),k="trd-openOrder",y=function(g){return(0,f.o)(k,g)},I=function(){var g=(0,l.zr)("open_order_status_toast",!0),_=g.data,F=g.setData,R=g.hasInitialized,L=(0,r.useCallback)(function(){F(!_)},[_,F]);return{enableToast:R?_:!1,toggleToast:L}},m=function(){var g=y().getI18n,_=(0,v.h)().enqueueNotification,F=I().enableToast,R=(0,P.A)(_,30),L=R.run,a=(0,s.d4)(function(i){return i.setting.layout}),M=(0,r.useMemo)(function(){return a===o.a0},[a]),C=(0,r.useCallback)(function(i){var S=i.side.toUpperCase(),T=i.type.replace(/[-_]/g,"");return"".concat(S).concat(T)},[]),c=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"CancelTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Canceled")});L(T)}},[C,g,L,F]),u=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"FillTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Filled")});L(T,{variant:"success"})}},[C,g,L,F]),h=(0,r.useCallback)(function(i){if(!(i.type==="LIQUIDATION"||!F)){var S=C(i),T=g("".concat(S,"PFTitle"),{defaultValue:"".concat(i.type," ").concat(i.side," Order Partially Filled")});L(T,{variant:"success"})}},[C,g,L,F]);return(0,r.useMemo)(function(){return{cancelOrderNotify:c,fillOrderNotify:u,fillOrderPartNotify:h}},[c,u,h])}}}[30877] }, + { id: "30877", chunkId: "57898", factory: {30877(K,T,e){e.d(T,{F:()=>i,z:()=>E});var t=e(41594),r=e.n(t),n=e(61523),s=e(92873),g=e(64041),c=e(17409),h=e(51471),p=e(40477),b="trd-openOrder",S=function(y){return(0,s.o)(b,y)},E=function(){var y=(0,h.zr)("open_order_status_toast",!0),C=y.data,f=y.setData,I=y.hasInitialized,d=(0,t.useCallback)(function(){f(!C)},[C,f]);return{enableToast:I?C:!1,toggleToast:d}},i=function(){var y=S().getI18n,C=(0,g.h)().enqueueNotification,f=E().enableToast,I=(0,p.A)(C,30),d=I.run,P=(0,n.d4)(function(a){return a.setting.layout}),M=(0,t.useMemo)(function(){return P===c.a0},[P]),k=(0,t.useCallback)(function(a){var o=a.side.toUpperCase(),u=a.type.replace(/[-_]/g,"");return"".concat(o).concat(u)},[]),A=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"CancelTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Canceled")});d(u)}},[k,y,d,f]),O=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"FillTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Filled")});d(u,{variant:"success"})}},[k,y,d,f]),x=(0,t.useCallback)(function(a){if(!(a.type==="LIQUIDATION"||!f)){var o=k(a),u=y("".concat(o,"PFTitle"),{defaultValue:"".concat(a.type," ").concat(a.side," Order Partially Filled")});d(u,{variant:"success"})}},[k,y,d,f]);return(0,t.useMemo)(function(){return{cancelOrderNotify:A,fillOrderNotify:O,fillOrderPartNotify:x}},[A,O,x])}}}[30877] }, + { id: "55401", chunkId: "29042", factory: {55401(S,f,o){o.r(f),o.d(f,{SoundNotificationProvider:()=>C});var c=o(31085),e=o(41594),x=o(17409),s=o(39116),E=function(a){var r,n;(r=a)===null||r===void 0||r.load();var u=(n=a)===null||n===void 0?void 0:n.play();u&&u.then(function(){var t;(t=a)===null||t===void 0||t.play()}).catch(function(){})},C=function(a){var r=a.children,n=(0,s.$$)(),u=n.interval,t=n.intervalFunc,h=(0,e.useRef)(null),N=(0,s.QZ)(),p=N.enableNotification,m=N.showNotification,d=(0,s.E$)(),l=d.notifications,P=d.lastNotifyTime,y=d.popNotification,j=(0,e.useCallback)(function(){var i,v=(i=h)===null||i===void 0?void 0:i.current;v&&(v.src="".concat(x.K5,"/static/spot-trade-ui/trade-sound.mp3"),E(v),t(y))},[t,y]);return(0,e.useEffect)(function(){var i;(!m||!p)&&(l.current=[]),l&&((i=l)===null||i===void 0?void 0:i.current.length)>0&&(u.current||j())},[P,p,j,l,u,m]),(0,c.jsxs)("div",{children:[r,(0,c.jsx)("div",{children:(0,c.jsx)("audio",{ref:h,children:(0,c.jsx)("source",{src:""})})})]})}}}[55401] }, + { id: "30877", chunkId: "57276", factory: {30877(Y,T,e){e.d(T,{F:()=>o,z:()=>N});var a=e(41594),k=e.n(a),P=e(61523),l=e(92873),O=e(64041),C=e(17409),h=e(51471),M=e(40477),p="trd-openOrder",R=function(n){return(0,l.o)(p,n)},N=function(){var n=(0,h.zr)("open_order_status_toast",!0),c=n.data,i=n.setData,F=n.hasInitialized,u=(0,a.useCallback)(function(){i(!c)},[c,i]);return{enableToast:F?c:!1,toggleToast:u}},o=function(){var n=R().getI18n,c=(0,O.h)().enqueueNotification,i=N().enableToast,F=(0,M.A)(c,30),u=F.run,L=(0,P.d4)(function(r){return r.setting.layout}),g=(0,a.useMemo)(function(){return L===C.a0},[L]),I=(0,a.useCallback)(function(r){var d=r.side.toUpperCase(),f=r.type.replace(/[-_]/g,"");return"".concat(d).concat(f)},[]),S=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"CancelTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Canceled")});u(f)}},[I,n,u,i]),A=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"FillTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Filled")});u(f,{variant:"success"})}},[I,n,u,i]),Q=(0,a.useCallback)(function(r){if(!(r.type==="LIQUIDATION"||!i)){var d=I(r),f=n("".concat(d,"PFTitle"),{defaultValue:"".concat(r.type," ").concat(r.side," Order Partially Filled")});u(f,{variant:"success"})}},[I,n,u,i]);return(0,a.useMemo)(function(){return{cancelOrderNotify:S,fillOrderNotify:A,fillOrderPartNotify:Q}},[S,A,Q])}}}[30877] }, + { id: "34122", chunkId: "57276", factory: {34122(Y,T,e){e.r(T),e.d(T,{OrderToastNotifyRegister:()=>N,OrderToastSwitch:()=>R});var a=e(31085),k=e(41594),P=e.n(k),l=e(71822),O=e(88478),C=e(48651),h=e(30877),M=e(34175),p=e(10157),R=function(){var o=(0,h.z)(),n=o.enableToast,c=o.toggleToast,i=(0,C.ud)().getI18n;return(0,a.jsxs)("div",{className:p.XE,onClick:c,children:[(0,a.jsx)("div",{className:p.IG,children:i("orderToastReminder",{defaultValue:"Toast Reminder"})}),(0,a.jsx)(O.A,{size:"small",checked:n})]})},N=function(){var o=(0,h.F)();return(0,k.useEffect)(function(){var n=[l.J.on("NEW",M.Zu),l.J.on("PARTIALLY_FILLED",o.fillOrderPartNotify),l.J.on("CANCELED",o.cancelOrderNotify),l.J.on("FILLED",o.fillOrderNotify),l.J.on("FILLED_NORMAL_ORDER",o.fillOrderNotify)];return function(){return n.forEach(function(c){return c()})}},[o]),null}}}[34122] }, + { id: "30877", chunkId: "76905", factory: {30877(L,T,e){e.d(T,{F:()=>_,z:()=>v});var r=e(41594),x=e.n(r),O=e(61523),h=e(92873),p=e(64041),k=e(17409),C=e(51471),I=e(40477),g="trd-openOrder",f=function(s){return(0,h.o)(g,s)},v=function(){var s=(0,C.zr)("open_order_status_toast",!0),c=s.data,o=s.setData,d=s.hasInitialized,n=(0,r.useCallback)(function(){o(!c)},[c,o]);return{enableToast:d?c:!1,toggleToast:n}},_=function(){var s=f().getI18n,c=(0,p.h)().enqueueNotification,o=v().enableToast,d=(0,I.A)(c,30),n=d.run,a=(0,O.d4)(function(t){return t.setting.layout}),S=(0,r.useMemo)(function(){return a===k.a0},[a]),i=(0,r.useCallback)(function(t){var l=t.side.toUpperCase(),u=t.type.replace(/[-_]/g,"");return"".concat(l).concat(u)},[]),y=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"CancelTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Canceled")});n(u)}},[i,s,n,o]),F=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"FillTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Filled")});n(u,{variant:"success"})}},[i,s,n,o]),m=(0,r.useCallback)(function(t){if(!(t.type==="LIQUIDATION"||!o)){var l=i(t),u=s("".concat(l,"PFTitle"),{defaultValue:"".concat(t.type," ").concat(t.side," Order Partially Filled")});n(u,{variant:"success"})}},[i,s,n,o]);return(0,r.useMemo)(function(){return{cancelOrderNotify:y,fillOrderNotify:F,fillOrderPartNotify:m}},[y,F,m])}}}[30877] }, + { id: "30877", chunkId: "49426", factory: {30877(R,T,e){e.d(T,{F:()=>y,z:()=>D});var t=e(41594),O=e.n(t),u=e(61523),i=e(92873),m=e(64041),d=e(17409),f=e(51471),a=e(40477),o="trd-openOrder",b=function(S){return(0,i.o)(o,S)},D=function(){var S=(0,f.zr)("open_order_status_toast",!0),M=S.data,P=S.setData,N=S.hasInitialized,_=(0,t.useCallback)(function(){P(!M)},[M,P]);return{enableToast:N?M:!1,toggleToast:_}},y=function(){var S=b().getI18n,M=(0,m.h)().enqueueNotification,P=D().enableToast,N=(0,a.A)(M,30),_=N.run,v=(0,u.d4)(function(n){return n.setting.layout}),l=(0,t.useMemo)(function(){return v===d.a0},[v]),s=(0,t.useCallback)(function(n){var g=n.side.toUpperCase(),c=n.type.replace(/[-_]/g,"");return"".concat(g).concat(c)},[]),r=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"CancelTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Canceled")});_(c)}},[s,S,_,P]),p=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"FillTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Filled")});_(c,{variant:"success"})}},[s,S,_,P]),h=(0,t.useCallback)(function(n){if(!(n.type==="LIQUIDATION"||!P)){var g=s(n),c=S("".concat(g,"PFTitle"),{defaultValue:"".concat(n.type," ").concat(n.side," Order Partially Filled")});_(c,{variant:"success"})}},[s,S,_,P]);return(0,t.useMemo)(function(){return{cancelOrderNotify:r,fillOrderNotify:p,fillOrderPartNotify:h}},[r,p,h])}}}[30877] }, + { id: "73335", chunkId: "40026", factory: {73335(ve,C,o){/** + * @license React + * react-jsx-runtime.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var O=o(41594),S=Symbol.for("react.element"),w=Symbol.for("react.fragment"),Q=Object.prototype.hasOwnProperty,Y=O.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,H={key:!0,ref:!0,__self:!0,__source:!0};function oe(B,$,z){var D,P={},F=null,ae=null;z!==void 0&&(F=""+z),$.key!==void 0&&(F=""+$.key),$.ref!==void 0&&(ae=$.ref);for(D in $)Q.call($,D)&&!H.hasOwnProperty(D)&&(P[D]=$[D]);if(B&&B.defaultProps)for(D in $=B.defaultProps,$)P[D]===void 0&&(P[D]=$[D]);return{$$typeof:S,type:B,key:F,ref:ae,props:P,_owner:Y.current}}C.Fragment=w,C.jsx=oe,C.jsxs=oe}}[73335] }, + { id: "22584", chunkId: "67656", factory: {22584(_,y,e){"use strict";e.d(y,{b:()=>i});var t=e(94917),i=function r(){"use strict";var a=this;(0,t._)(this,r),this.cbs={},this.on=function(s,n){var o=a;return a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].push(n),function(){return o.off(s,n)}},this.off=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s]=a.cbs[s].filter(function(o){return o!==n})},this.emit=function(s,n){a.cbs[s]||(a.cbs[s]=[]),a.cbs[s].forEach(function(o){return typeof o=="function"&&o(n)})}}}}[22584] }, + { id: "5558", chunkId: "32536", factory: {5558(ee,K,t){t.d(K,{$Q:()=>T,Ai:()=>S,CC:()=>R,H9:()=>h,Ub:()=>O,cg:()=>A,qS:()=>F});var P=t(61489),n=t(37230),o=t(53548);const S={symbol:"s",clientOrderId:"c",side:"S",orderType:"o",timeInForce:"f",quantity:n.X9.number("q"),price:n.X9.number("p"),stopPrice:n.X9.number("P"),icebergQuantity:n.X9.number("F"),orderListId:"g",originalClientOrderId:"C",executionType:"x",orderStatus:"X",orderRejectReason:"r",orderId:"i",LastExecutedQuantity:n.X9.number("l"),cumulativeFilledQuantity:n.X9.number("z"),lastPrice:n.X9.number("L"),commissionAmount:n.X9.number("n"),commissionAsset:"N",transactionTime:"T",tradeId:"t",isOnOrderBook:"w",isMaker:"m",orderCreationTime:"O",cumulativeQuoteQuantity:n.X9.number("Z"),lastQuoteQuantity:n.X9.number("Y"),quoteOrderQty:n.X9.number("Q"),activatePrice:n.X9.number("AP"),callbackRate:n.X9.number("cr"),trailingDelta:"d",trailingTime:"D",workingTime:"W",isSor:"uS",pegPriceType:"gP",pegOffsetType:"gOT",pegOffsetValue:"gOV",peggedPrice:"gp"},T=Object.keys(S),I={symbol:"s",insertTime:"T",type:"o",side:"S",price:n.X9.number("p"),avgPrice:n.X9.number("ap"),executedPrice:n.X9.number("ap"),executedQty:n.X9.number("z"),origQty:"q",stopPrice:n.X9.number("sp"),operate:"x",status:"X",orderId:"i",clientOrderId:"c",orderType:"o",origType:"ot",timeInForce:"f",goodTillDate:n.X9.number("gtd"),bidNotional:n.X9.number("b"),askNotional:n.X9.number("a"),workingType:"wt",positionSide:"ps",reduceOnly:"R",strategyId:"si",strategySubId:"ss",strategyType:"st",closePosition:"cp",activatePrice:n.X9.number("AP"),callbackRate:n.X9.number("cr"),priceRate:n.X9.number("cr"),priceMatch:"pm",updateTime:"T"},l=Object.keys(I),f={algoId:"aid",clientAlgoId:"caid",algoType:"at",orderType:"o",symbol:"s",side:"S",positionSide:"ps",timeInForce:"f",quantity:"q",algoStatus:"X",actualOrderId:"ai",actualPrice:"ap",actualQty:"aq",actualType:"act",triggerPrice:"tp",price:"p",icebergQuantity:"iq",tpTriggerPrice:"tptp",tpPrice:"tpp",tpOrderType:"tpot",slTriggerPrice:"sltp",slPrice:"slp",slOrderType:"slot",selfTradePreventionMode:"V",workingType:"wt",priceMatch:"pm",closePosition:"cp",priceProtect:"pP",reduceOnly:"R",activatePrice:"AP",callbackRate:"cr",goodTillDate:n.X9.number("gtd"),triggerTime:n.X9.number("tt"),updateTime:n.X9.now()},y=Object.keys(f),g={symbol:"s",strategyId:"si",newClientStrategyId:"c",side:"S",strategyType:"st",timeInForce:"f",origQty:"q",price:"p",stopPrice:"sp",strategyStatus:"os",bookTime:"T",reduceOnly:"R",workingType:"wt",positionSide:"ps",closePosition:"cp",activatePrice:"AP",priceRate:n.X9.number("cr"),priceMatch:"pm",orderId:"i",parentStrategyId:"pi",parentSubId:"PS",parentStrategyType:"pt",goodTillDate:n.X9.number("gtd")},r=Object.keys(g),d={symbol:"s",strategyClientOrderId:"c",strategyType:"st",strategyId:"si",takeProfitPrice:"tpp",stopLossPrice:"slp",takeProfitTriggerPrice:"tptp",stopLossTriggerPrice:"sltp"},i=Object.keys(d);function A(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:"executionReport",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>j((0,n.Li)(V,S,T)))},_,W);return(0,o.G)(X)}function R(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:"ORDER_TRADE_UPDATE",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{if(V.fs&&V.fs!==m.PMFuturesSource)return;const c=(0,n.Li)(V.o,I,l);return(V.o.X==="PARTIALLY_FILLED"||V.o.x==="AMENDMENT")&&(c.updateTime=V.E),j(c)})},_,W);return(0,o.G)(X)}function F(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:"ALGO_UPDATE",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{const c="ao"in V?V.ao:V.o;return j((0,n.Li)(c,f,y))})},_,W);return(0,o.G)(X)}function O(m){const _=(0,P.s)(m,{type:m.type,eventType:"CONDITIONAL_ORDER_TRADE_UPDATE",transform:W=>X=>W.coalscedMsg.forEach(({data:Q})=>{if(!(Q.fs&&Q.fs!==m.PMFuturesSource))return X((0,n.Li)(Q.so,g,r))})});return(0,o.G)(_)}function h(m,_,W){const X=(0,P.s)(m,{type:m.type,eventType:"OTOCO_ORDER_UPDATE",transform:Q=>j=>Q.coalscedMsg.forEach(({data:V})=>{j((0,n.Li)(V.su,d,i))})},_,W);return(0,o.G)(X)}}}[5558] }, + { id: "70020", chunkId: "3314", factory: {70020(D,l,t){"use strict";t.d(l,{Q:()=>d});var e=t(41594),a=t.n(e),r=t(80065),n=t(96636),i=t(51471),s="open-order-notification-sound-open",c=(0,r.A)(function(y){return{enableNotification:!1,setNotification:function(p){return y({enableNotification:p})}}}),d=function(){var y=(0,e.useState)(!1),p=y[0],T=y[1],S=(0,i.zr)(s,!1),E=S.data,A=S.setData,O=S.hasInitialized,m=c(),g=m.enableNotification,u=m.setNotification,o=(0,n.Z)().isEUFuturesUrl;(0,e.useEffect)(function(){p||O&&(u(E),T(!0))},[E,p,O,u]);var f=(0,e.useCallback)(function(){u(!g),A(!g)},[g,A,u]);return{initialized:p,showNotification:!o,enableNotification:o?!1:g,toggleNotification:f}}}}[70020] }, + { id: "4189", chunkId: "14105", factory: {4189(U,P,e){e.d(P,{zH:()=>D,nT:()=>h,u7:()=>Q,$t:()=>u,Qj:()=>T});var s=e(41594),l=e(87017),y=e(48187),m=e(57861),L=e(41466);const M=async g=>{let f;try{f=await(0,m.post)(`/bapi/futures/v1/private/future/order/open-orders?${(0,L.E2)(g||{})}`,{})}catch{f={success:!1}}return f};var S=e(43335),R=e(26860),N=e(84266),I=e(16921),C=e(90291),A=e(47738),a=e(95541),F=e(71822);const d=(g,f,i)=>{const{isCM:b}=i,p=[...g];return f.forEach(O=>{const{symbol:k,status:B,strategyType:G=null,clientOrderId:K}=O,z={...O,strategyType:G},x=p.findIndex(V=>V.clientOrderId===K&&V.symbol===k);switch(B){case"NEW":F.J.emit("NEW",{isCM:b,clientOrderId:K}),x!==-1?p[x]=z:p.unshift(z);break;case"PARTIALLY_FILLED":x!==-1&&(O.operate!=="AMENDMENT"&&F.J.emit("PARTIALLY_FILLED",p[x]),p[x]=z);break;case"CANCELED":case"EXPIRED":case"EXPIRED_IN_MATCH":x!==-1&&(F.J.emit("CANCELED",p[x]),p.splice(x,1));break;case"FILLED":x!==-1&&(F.J.emit("FILLED_NORMAL_ORDER",p[x]),p.splice(x,1));break;default:break}}),p},v=({isPM2:g,isCM:f,stream:i,queryClient:b,copyTradingPayload:p})=>{const O=p||{},k=f?a.CX:(0,a.kc)({copyTradingPayload:O}),B=K=>{b.setQueryData(S.Bz.OPEN_ORDERS(f,O),z=>({success:!0,data:d(z?.data||[],K,{isCM:f})}))};k.addWorker(B);const G=i.getUserOrderStream({isPM2:g,params:O}).subscribe(K=>{k.push(K)});return()=>{G(),k.removeWorker(B)}},o=(0,A.Y)({queryKey:S.Bz.OPEN_ORDERS,subscribeToStreamFn:v}),T=async({isCM:g,copyTradingPayload:f})=>g?(0,y.mp)():M(f),u=({enabled:g,isCM:f,isPM2:i,getSDK:b,queryClient:p,copyTradingPayload:O})=>{g&&o({getSDK:b,queryClient:p,isCM:f,isPM2:i,copyTradingPayload:O})},D=(g,{disableWs:f,...i}={})=>{const b=(0,R.n)(),{isExistFutureAccount:p}=(0,N.Py)(),O=(0,I.by)(),k=!!(b&&p),B=(0,I.Gw)(),G=(0,l.useQueryClient)(),K=(0,C.OE)();return(0,s.useEffect)(function(){u({enabled:k&&!f,isCM:g,isPM2:O,getSDK:K,queryClient:G,copyTradingPayload:B})},[k,g,O,K,G,f,B]),(0,l.useQuery)(S.Bz.OPEN_ORDERS(g,B),()=>T({isCM:g,copyTradingPayload:B}),{enabled:k,cacheTime:1/0,staleTime:1/0,notifyOnChangeProps:["data"],...i})},Q=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>{f.invalidateQueries(S.Bz.OPEN_ORDERS(g,i))},[i,g,f])},h=g=>{const f=(0,l.useQueryClient)(),i=(0,I.Gw)();return(0,s.useCallback)(()=>f.getQueryData(S.Bz.OPEN_ORDERS(g,i))?.data?.length||0,[i,g,f])}}}[4189] }, + { id: "37230", chunkId: "32536", factory: {37230(ee,K,t){t.d(K,{Li:()=>S,Rr:()=>n,X9:()=>o});const P={isolated:!0,cross:!1};function n(T){const I=T.split(".");return l=>{for(let f=0;fNumber(I(l))},text(T){const I=n(T);return l=>I(l)},isolatedVal(T){const I=n(T);return l=>P[I(l)]},now(){return T=>Date.now()}};function S(T,I,l){const f=Object.create(null);for(let y=0;y entry.id === String(id) && (chunkId === undefined || entry.chunkId === String(chunkId))); + if (matches.length !== 1) throw new Error('Native notification fixture needs one exact module and chunk'); + return matches[0].factory; +} diff --git a/test/helpers/binance-order-notifications.js b/test/helpers/binance-order-notifications.js new file mode 100644 index 0000000..720e94a --- /dev/null +++ b/test/helpers/binance-order-notifications.js @@ -0,0 +1,344 @@ +import assert from 'node:assert/strict'; +import * as React from 'react'; +import { createRoot } from 'react-dom/client'; +import { act } from 'react-dom/test-utils'; +import { jsx, jsxs } from 'react/jsx-runtime'; +import { JSDOM } from 'jsdom'; + +import { createNativeOrderNotificationFactory } from '../fixtures/binance-order-notifications/original-factories.js'; + +/** The SDK boundary retains distinct UM and CM subscribers and exact payloads. */ +export function createNotificationStreams() { + const channels = new Map([[false, new Set()], [true, new Set()]]); + const subscriptions = []; + return { + subscriptions, + getSDK({ isCM }) { + assert.equal(typeof isCM, 'boolean'); + return { + getUserOrderStream(options) { + return { + subscribe(listener) { + channels.get(isCM).add(listener); + subscriptions.push({ isCM, options }); + return () => channels.get(isCM).delete(listener); + }, + }; + }, + }; + }, + emit(payload, isCM = false) { + for (const listener of channels.get(isCM)) listener(payload); + }, + size(isCM) { + return channels.get(isCM).size; + }, + }; +} + +/** Model only the native persistent preference boundary, with real React state. */ +function createPreferences({ toastEnabled, soundEnabled }) { + const values = new Map([ + ['open_order_status_toast', toastEnabled], + ['open-order-notification-sound-open', soundEnabled], + ]); + return { + zr(key) { + assert.equal(values.has(key), true, `Unknown native preference ${key}`); + const [data, setData] = React.useState(values.get(key)); + return { data, setData, hasInitialized: true }; + }, + }; +} + +/** Zustand is an external state boundary; subscribers receive actual snapshots. */ +function createExternalStore(initialize) { + const subscribers = new Set(); + let state = initialize((change) => { + state = { ...state, ...change }; + for (const listener of subscribers) listener(); + }); + const subscribe = (listener) => { + subscribers.add(listener); + return () => subscribers.delete(listener); + }; + const getSnapshot = () => state; + return () => React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot); +} + +export function ordinaryNotificationOrder(overrides = {}) { + return { + symbol: 'BTCUSDT', + orderId: 42, + clientOrderId: 'manual-order-42', + type: 'LIMIT', + orderType: 'LIMIT', + origType: 'LIMIT', + side: 'BUY', + status: 'FILLED', + operate: 'TRADE', + ...overrides, + }; +} + +/** Load complete captured factories, rejecting every undeclared host boundary. */ +export function createNativeNotificationModules(dependencies, factories = new Map()) { + const cache = new Map(); + function requireModule(id) { + if (dependencies.has(id)) return dependencies.get(id); + if (cache.has(id)) return cache.get(id).exports; + const factory = factories.get(id) ?? createNativeOrderNotificationFactory(id); + const module = { exports: {} }; + cache.set(id, module); + factory(module, module.exports, requireModule); + return module.exports; + } + requireModule.d = (exports, getters) => { + for (const [key, get] of Object.entries(getters)) Object.defineProperty(exports, key, { enumerable: true, get }); + }; + requireModule.r = (exports) => Object.defineProperty(exports, '__esModule', { value: true }); + requireModule.n = (exports) => () => exports; + return requireModule; +} + +/** Execute the captured SDK schema and parser while replacing only transport creation. */ +export function createNativeOrderDecoder(source = 'UM') { + const transport = Object.freeze({ kind: 'controlled-order-transport' }); + let configuration; + const dependencies = new Map([ + [61489, { + s(client, options) { + assert.equal(client.PMFuturesSource, source); + configuration = options; + return transport; + }, + }], + [53548, { G: (value) => { + assert.equal(value, transport); + return value; + } }], + ]); + const native = createNativeNotificationModules(dependencies)(5558); + assert.equal(native.CC({ type: 'futures', PMFuturesSource: source }), transport); + return { + eventType: configuration.eventType, + decode(packet) { + const orders = []; + configuration.transform(packet)((order) => orders.push(order)); + return orders; + }, + }; +} + +/** + * Run Binance's actual cache-to-toast adapter. The query cache, stream lifecycle, + * and batching transport are external boundaries; native 4189 owns event choice. + */ +export function attachNativeOrderEventHost({ streams, emitter }) { + const queries = new Map(); + const key = (isCM) => `orders:${isCM}`; + const queryClient = { + setQueryData(queryKey, update) { + queries.set(queryKey, update(queries.get(queryKey))); + }, + }; + const batcher = () => { + const workers = new Set(); + return { + addWorker: (worker) => workers.add(worker), + removeWorker: (worker) => workers.delete(worker), + push(order) { + for (const worker of workers) worker(Array.isArray(order) ? order : [order]); + }, + }; + }; + const cmBatcher = batcher(); + const umBatcher = batcher(); + const cleanups = []; + const dependencies = new Map([ + [41594, React], + [87017, {}], + [48187, { mp: () => { throw new Error('Financial network access is outside the notification fixture'); } }], + [57861, { post: () => { throw new Error('Financial network access is outside the notification fixture'); } }], + [41466, {}], + [43335, { Bz: { OPEN_ORDERS: key } }], + [26860, {}], + [84266, {}], + [16921, {}], + [90291, {}], + [47738, { + Y: ({ subscribeToStreamFn }) => (options) => { + cleanups.push(subscribeToStreamFn({ + ...options, + stream: options.getSDK({ isCM: options.isCM }), + })); + }, + }], + [95541, { CX: cmBatcher, kc: () => umBatcher }], + [71822, { J: emitter }], + ]); + const native = createNativeNotificationModules(dependencies)(4189); + for (const isCM of [false, true]) { + native.$t({ + enabled: true, + isCM, + isPM2: false, + getSDK: (options) => streams.getSDK(options), + queryClient, + copyTradingPayload: {}, + }); + } + return { + orders: (isCM = false) => queries.get(key(isCM))?.data ?? [], + dispose() { + for (const cleanup of cleanups) cleanup(); + }, + }; +} + +/** + * Render complete captured Binance hooks and providers with real React effects. + * Only host I/O, preferences, and external store boundaries are replaced. + */ +export async function createNotificationHarness(t, { + patched = true, + pathname = '/en/futures/BTCUSDT', + toastEnabled = true, + soundEnabled = true, + variantChunk = '37511', + driftModule = null, + firstPlayPending = false, + inspectSoundQueue = false, +} = {}) { + t.mock.timers.enable({ apis: ['setTimeout', 'Date'], now: 1800000000000 }); + const dom = new JSDOM('
', { url: `https://www.binance.com${pathname}` }); + const saved = new Map(); + for (const [key, value] of Object.entries({ + window: dom.window, + self: dom.window, + document: dom.window.document, + location: dom.window.location, + IS_REACT_ACT_ENVIRONMENT: true, + })) { + saved.set(key, Object.getOwnPropertyDescriptor(globalThis, key)); + Object.defineProperty(globalThis, key, { configurable: true, writable: true, value }); + } + const streams = createNotificationStreams(); + const toasts = []; + const audio = []; + let resolveFirstPlay; + const firstPlay = firstPlayPending ? new Promise((resolve) => { resolveFirstPlay = resolve; }) : null; + let playCount = 0; + dom.window.HTMLMediaElement.prototype.load = function load() { + audio.push({ kind: 'load', pathname: dom.window.location.pathname }); + }; + dom.window.HTMLMediaElement.prototype.play = function play() { + playCount += 1; + audio.push({ kind: 'play', pathname: dom.window.location.pathname }); + return playCount === 1 && firstPlay ? firstPlay : Promise.resolve(); + }; + const copyTradingParams = Object.freeze({}); + const dependencies = new Map([ + [41594, React], + [31085, { jsx, jsxs }], + [75510, { _: (array) => [...array] }], + [94917, { _: (instance, Constructor) => assert.equal(instance instanceof Constructor, true) }], + [17409, { a0: 'classic', K5: 'https://static.test.invalid' }], + [61523, { d4: (selector) => selector({ setting: { layout: 'classic' } }) }], + [92873, { o: () => ({ getI18n: (_key, options) => options.defaultValue }) }], + [64041, { h: () => ({ enqueueNotification: (message, options) => toasts.push({ message, options }) }) }], + [51471, createPreferences({ toastEnabled, soundEnabled })], + [80065, { A: createExternalStore }], + [96636, { Z: () => ({ isEUFuturesUrl: false }) }], + [16921, { Gw: () => copyTradingParams }], + [72363, { Ri: (options) => streams.getSDK(options) }], + [79515, { nH: () => true, Py: () => ({ isExistFutureAccount: true }), ON: () => ({ isPM2: false }) }], + [88478, { A: () => null }], + [48651, { ud: () => ({ getI18n: (_key, options) => options.defaultValue }) }], + [34175, { Zu: () => undefined }], + [10157, { XE: 'notification-switch', IG: 'notification-label' }], + ]); + const scope = patched + ? (await import('../../src/binance-orderbook-trade/order-notifications/runtime.js')).createOrderNotificationScope() + : null; + const factories = new Map(); + for (const id of [30877, 39116, 55401, 40477, 70020, 22584, 34122]) { + const original = createNativeOrderNotificationFactory(id, id === 30877 ? variantChunk : undefined); + const target = scope?.targets[id]; + if (target && id === driftModule) { + const driftedFactory = function changedNativeFactory(...args) { + return Reflect.apply(original, this, args); + }; + assert.throws(() => target.replace(driftedFactory), /source does not match a pinned public module/); + target.onFailure('source_mismatch'); + factories.set(id, driftedFactory); + } else if (target) { + factories.set(id, target.replace(original)); + target.onCapture(); + } else { + factories.set(id, original); + } + } + const requireModule = createNativeNotificationModules(dependencies, factories); + const NativeEmitter = requireModule(22584).b; + const emitter = new NativeEmitter(); + dependencies.set(71822, { J: emitter }); + const nativeHost = attachNativeOrderEventHost({ streams, emitter }); + const Provider = requireModule(55401).SoundNotificationProvider; + const ToastRegister = requireModule(34122).OrderToastNotifyRegister; + let inspectedQueue; + function SoundQueueObserver() { + inspectedQueue = requireModule(39116).E$().notifications; + return null; + } + const root = createRoot(dom.window.document.getElementById('root')); + await act(async () => root.render(React.createElement(Provider, null, + React.createElement(ToastRegister), + inspectSoundQueue ? React.createElement(SoundQueueObserver) : null, + ))); + let disposed = false; + async function dispose() { + if (disposed) return; + disposed = true; + await act(async () => root.unmount()); + nativeHost.dispose(); + t.mock.timers.reset(); + dom.window.close(); + for (const [key, descriptor] of saved) { + if (descriptor) Object.defineProperty(globalThis, key, descriptor); + else delete globalThis[key]; + } + } + t.after(dispose); + return { + audio, + toasts, + streams, + scope, + dispose, + nativeHost, + queueSnapshot: () => [...inspectedQueue.current], + async emitStream(order, { isCM = false } = {}) { + await act(async () => streams.emit(order, isCM)); + }, + async emitToast(order, event = 'FILLED_NORMAL_ORDER') { + await act(async () => emitter.emit(event, order)); + }, + async notify(order, options) { + await act(async () => { + streams.emit({ ...order, status: 'NEW' }, options?.isCM ?? false); + streams.emit(order, options?.isCM ?? false); + }); + }, + async advance(milliseconds) { + await act(async () => t.mock.timers.tick(milliseconds)); + }, + navigate(nextPathname) { + dom.window.history.pushState({}, '', nextPathname); + }, + async settleFirstPlay() { + assert.equal(firstPlayPending, true); + await act(async () => resolveFirstPlay()); + }, + }; +} diff --git a/test/unit/binance-chart-storage-runtime.test.js b/test/unit/binance-chart-storage-runtime.test.js index 943ca74..0da3ccd 100644 --- a/test/unit/binance-chart-storage-runtime.test.js +++ b/test/unit/binance-chart-storage-runtime.test.js @@ -7,6 +7,8 @@ import { observeChartStorageBootstrap } from '../../src/binance-orderbook-trade/ import { startChartStorageOptimizer } from '../../src/binance-orderbook-trade/chart-storage/runtime.js'; import { createNativeMirrorFactory } from '../fixtures/binance-chart-storage/mirror-scoped-callback.js'; import { createNativeDrawingSaveFactory } from '../fixtures/binance-chart-storage/drawing-save-scoped.js'; +import { createNativeOrderNotificationFactory } from '../fixtures/binance-order-notifications/original-factories.js'; +import { createOrderNotificationScope } from '../../src/binance-orderbook-trade/order-notifications/runtime.js'; const PAGE = 'https://www.binance.com/zh-CN/futures/BTCUSDT'; const QUEUE = 'webpackChunkfutures_trade_ui'; @@ -90,8 +92,8 @@ function installPage(t, { url = PAGE, framed = false, queue } = {}) { observers.push(observer); return observer; }, - start() { - const session = startChartStorageOptimizer(); + start(options) { + const session = startChartStorageOptimizer(options); sessions.push(session); return session; }, @@ -197,6 +199,108 @@ test('user keeps the original factory executable when strict source registration assert.deepEqual(stopped, { status: 'native', reason: 'source_mismatch', attempts: 1, matches: 0, executions: 0, phase: 'stopped', writer: EMPTY_WRITER, drawingScope: WAITING_DRAWING }); }); +/** Render-free factory execution proves the shared real Rspack registration path. */ +function registerNotificationFactories({ mismatchId } = {}) { + const originals = Object.fromEntries([['30877', '37511'], ['39116', '3314'], ['55401', '29042']] + .map(([id, chunk]) => [id, createNativeOrderNotificationFactory(id, chunk)])); + if (mismatchId) originals[mismatchId] = module => { module.exports = { nativeDrift: mismatchId }; }; + const inactiveIds = [61523, 64041, 51471, 40477, 16921, 72363, 70020]; + const inactive = Object.fromEntries(inactiveIds.map(id => [id, module => { module.exports = Object.freeze({}); }])); + globalThis[QUEUE].push([['notification-registration'], { ...inactive, ...originals }]); + return originals; +} + +for (const stopMirrorFirst of [false, true]) { + test(`user captures notification modules independently when mirror stopped=${stopMirrorFirst}`, async t => { + // Given one shared observer owns chart and notification targets before the real host runtime. + const page = installPage(t); + const notifications = createOrderNotificationScope(); + const session = page.start({ additionalTargets: notifications.targets }); + if (stopMirrorFirst) await session.stop(); + const host = registerPinnedChartFactories(); + registerNotificationFactories(); + + // When native notification factories execute before the remaining chart factories. + host.require(55401); + host.require(30877); + host.require(76535); + host.require(70940); + const status = notifications.snapshot(); + + // Then both notification paths activate and native queue ownership is restored exactly once. + assert.equal(status.toastActive, true); + assert.equal(status.soundActive, true); + assert.deepEqual(Object.values(status.modules).map(target => target.status), ['active', 'active', 'active']); + assert.equal(session.snapshot().drawingScope.status, 'active'); + assert.equal(session.snapshot().status, stopMirrorFirst ? 'native' : 'active'); + assert.equal(Object.hasOwn(Object.getOwnPropertyDescriptor(globalThis[QUEUE], 'push'), 'value'), true); + }); +} + +for (const mismatchId of ['30877', '39116', '55401']) { + test(`user keeps storage and native source drift isolated for notification module ${mismatchId}`, t => { + // Given one notification factory changed upstream while all chart factories still match. + const page = installPage(t); + const notifications = createOrderNotificationScope(); + const session = page.start({ additionalTargets: notifications.targets }); + const host = registerPinnedChartFactories(); + const originals = registerNotificationFactories({ mismatchId }); + + // When all three notification factories and the chart factories execute. + for (const id of ['39116', '55401', '30877', '70940']) host.require(id); + const status = notifications.snapshot(); + + // Then only the changed notification target rejects and both storage protections stay active. + assert.deepEqual(status.modules[mismatchId], { status: 'source_mismatch', reason: 'source_mismatch', attempts: 1, matches: 0 }); + assert.equal(status.toastActive, mismatchId !== '30877'); + assert.equal(status.soundActive, mismatchId === '30877'); + assert.deepEqual(host.require(mismatchId), { nativeDrift: mismatchId }); + assert.equal(host.require.m[mismatchId], originals[mismatchId]); + assert.equal(session.snapshot().status, 'active'); + assert.equal(session.snapshot().drawingScope.status, 'active'); + }); +} + +test('user sees independent notification expiry at the original shared capture deadline', async t => { + // Given the notification targets have not registered and the mirror was stopped early. + const page = installPage(t); + t.mock.timers.enable({ apis: ['setTimeout'] }); + const notifications = createOrderNotificationScope(); + const session = page.start({ additionalTargets: notifications.targets }); + t.mock.timers.tick(20_000); + await session.stop(); + + // When the remaining ten seconds expire without resetting the shared deadline. + t.mock.timers.tick(10_000); + const status = notifications.snapshot(); + + // Then no notification target activates or touches storage and the queue observer retires. + assert.equal(status.toastActive, false); + assert.equal(status.soundActive, false); + for (const target of Object.values(status.modules)) { + assert.deepEqual(target, { status: 'unavailable', reason: 'capture_deadline', attempts: 0, matches: 0 }); + } + assert.equal(Object.hasOwn(globalThis, QUEUE), false); + assert.equal(session.snapshot().reason, 'manual'); +}); + +test('user gets explicit notification unavailability when injection is too late', async t => { + // Given the host has already registered a chunk before installation. + const page = installPage(t, { queue: [[['existing'], {}]] }); + const notifications = createOrderNotificationScope(); + + // When the normal installer tries to attach the shared capture targets. + const session = page.start({ additionalTargets: notifications.targets }); + await session.stop(); + + // Then all notifications stay native with a clear late-bootstrap outcome. + for (const target of Object.values(notifications.snapshot().modules)) { + assert.deepEqual(target, { status: 'unavailable', reason: 'bootstrap_unavailable', attempts: 0, matches: 0 }); + } + assert.equal(globalThis[QUEUE].length, 1); + assert.equal(Object.hasOwn(globalThis[QUEUE], 'push'), false); +}); + test('user restores the exact pinned factory when stopping before its first execution', async t => { // Given the real runtime owns an empty queue and receives the complete pinned factory. const page = installPage(t);