gloomberb 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scheduleConfigSave } from "../state/config-save-scheduler";
|
|
2
|
+
import { normalizeStoredBaseCurrency } from "../utils/base-currency";
|
|
2
3
|
import { debugLog } from "../utils/debug-log";
|
|
3
4
|
import { fnv1aHex, stableStringify } from "../utils/hash";
|
|
4
5
|
import type { AppConfig, BrokerInstanceConfig, SavedLayout } from "../types/config";
|
|
@@ -39,6 +40,16 @@ import {
|
|
|
39
40
|
knownBrokerHoldings,
|
|
40
41
|
type KnownBrokerHoldings,
|
|
41
42
|
} from "../brokers/broker-portfolio-sync";
|
|
43
|
+
import {
|
|
44
|
+
isLocalOnlyInstance,
|
|
45
|
+
isLocalOnlyPortfolio,
|
|
46
|
+
localOnlyScope,
|
|
47
|
+
publicConfig,
|
|
48
|
+
publicMarketData,
|
|
49
|
+
publicTickers,
|
|
50
|
+
withLocalOnlyEntries,
|
|
51
|
+
type LocalOnlyScope,
|
|
52
|
+
} from "./local-only";
|
|
42
53
|
|
|
43
54
|
/**
|
|
44
55
|
* What a saved layout mirrors from the live session rather than from the
|
|
@@ -612,9 +623,11 @@ function collectCoreCollectionsPayload(
|
|
|
612
623
|
financials: Map<string, TickerFinancials>,
|
|
613
624
|
exchangeRates: Map<string, number>,
|
|
614
625
|
brokerAccounts: Record<string, BrokerAccount[]>,
|
|
626
|
+
/** The financials whose quotes may be uploaded: without those a source that keeps its market data local supplied. */
|
|
627
|
+
quoted: Map<string, TickerFinancials> = financials,
|
|
615
628
|
) {
|
|
616
629
|
const records = [...tickers.values()]
|
|
617
|
-
.map((ticker) => sanitizeTickerMetadata(ticker.metadata,
|
|
630
|
+
.map((ticker) => sanitizeTickerMetadata(ticker.metadata, quoted.get(ticker.metadata.ticker)))
|
|
618
631
|
.filter(isSyncableTicker);
|
|
619
632
|
|
|
620
633
|
// Only the USD anchor goes on the wire, as before: readers of the snapshot
|
|
@@ -873,14 +886,38 @@ interface LayoutMergeContext {
|
|
|
873
886
|
pushedAt?: string;
|
|
874
887
|
}
|
|
875
888
|
|
|
889
|
+
/** A pulled config without profiles and portfolios that are local-only here (an older build may have pushed them). */
|
|
890
|
+
function publicConfigPayload(payload: Record<string, unknown>, scope: LocalOnlyScope): Record<string, unknown> {
|
|
891
|
+
const without = (entries: unknown, isLocal: (entry: Record<string, unknown>) => boolean) => (
|
|
892
|
+
Array.isArray(entries) ? entries.filter((entry) => !isRecord(entry) || !isLocal(entry)) : entries
|
|
893
|
+
);
|
|
894
|
+
return {
|
|
895
|
+
...payload,
|
|
896
|
+
...("brokerInstances" in payload ? { brokerInstances: without(payload.brokerInstances, (entry) => isLocalOnlyInstance({ id: String(entry.id) }, scope)) } : {}),
|
|
897
|
+
...("portfolios" in payload ? { portfolios: without(payload.portfolios, (entry) => isLocalOnlyPortfolio(entry as unknown as Portfolio, scope)) } : {}),
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
|
|
876
901
|
function mergeConfigPayload(
|
|
877
902
|
config: AppConfig,
|
|
878
903
|
payload: unknown,
|
|
879
904
|
baselineConfig: AppConfig = config,
|
|
880
905
|
lastSyncedPayload?: unknown,
|
|
881
906
|
layoutContext: LayoutMergeContext = {},
|
|
907
|
+
scope: LocalOnlyScope | null = null,
|
|
882
908
|
): AppConfig | null {
|
|
883
909
|
if (!isRecord(payload)) return null;
|
|
910
|
+
if (scope) {
|
|
911
|
+
// Merged as if the local-only profiles and portfolios were not here, then put back as they were.
|
|
912
|
+
const local = publicConfig(config, scope);
|
|
913
|
+
const merged = mergeConfigPayload(local, publicConfigPayload(payload, scope), baselineConfig === config ? local : publicConfig(baselineConfig, scope),
|
|
914
|
+
lastSyncedPayload, layoutContext);
|
|
915
|
+
return merged && {
|
|
916
|
+
...merged,
|
|
917
|
+
brokerInstances: withLocalOnlyEntries(merged.brokerInstances, config.brokerInstances, (instance) => isLocalOnlyInstance(instance, scope)),
|
|
918
|
+
portfolios: withLocalOnlyEntries(merged.portfolios, config.portfolios, (portfolio) => isLocalOnlyPortfolio(portfolio, scope)),
|
|
919
|
+
};
|
|
920
|
+
}
|
|
884
921
|
const next: AppConfig = { ...config };
|
|
885
922
|
// Two guards, both needed. baselineConfig catches edits made while this pull
|
|
886
923
|
// was in flight; lastSyncedPayload catches edits made while the app was not
|
|
@@ -900,6 +937,8 @@ function mergeConfigPayload(
|
|
|
900
937
|
};
|
|
901
938
|
|
|
902
939
|
assign("baseCurrency");
|
|
940
|
+
// A device on an older version may still send a name or sign (Canadian dollar, CA$): it applies as its code, as a saved one loads.
|
|
941
|
+
if (typeof next.baseCurrency === "string" && next.baseCurrency.trim()) next.baseCurrency = normalizeStoredBaseCurrency(next.baseCurrency);
|
|
903
942
|
assign("refreshIntervalMinutes");
|
|
904
943
|
if ("portfolios" in payload && canApply("portfolios")) {
|
|
905
944
|
const syncedPortfolios = payload.portfolios;
|
|
@@ -1148,6 +1187,8 @@ interface PulledTickersMerge {
|
|
|
1148
1187
|
/** This device's live broker portfolios, each with the side holding its latest broker sync. */
|
|
1149
1188
|
brokerPortfolios: ReadonlyMap<string, "local" | "pulled">;
|
|
1150
1189
|
known: KnownBrokerHoldings;
|
|
1190
|
+
/** Connections kept on this device: their portfolios' holdings here outlast any pull. */
|
|
1191
|
+
localOnly: LocalOnlyScope | null;
|
|
1151
1192
|
}
|
|
1152
1193
|
|
|
1153
1194
|
/**
|
|
@@ -1224,7 +1265,7 @@ function mergePulledTicker(
|
|
|
1224
1265
|
const portfolios: string[] = [];
|
|
1225
1266
|
for (const portfolioId of portfolioIds) {
|
|
1226
1267
|
const brokerSide = merge.brokerPortfolios.get(portfolioId);
|
|
1227
|
-
const takePulled = brokerSide
|
|
1268
|
+
const takePulled = merge.localOnly?.portfolioIds.has(portfolioId) ? false : brokerSide
|
|
1228
1269
|
? brokerSide === "pulled"
|
|
1229
1270
|
: !comparable || sameAsStored(
|
|
1230
1271
|
holdingIn(localPositions, localLinks, portfolioId),
|
|
@@ -1269,7 +1310,7 @@ export const coreConfigSyncContributor: SyncContributor = {
|
|
|
1269
1310
|
id: CORE_CONFIG_CONTRIBUTOR_ID,
|
|
1270
1311
|
schemaVersion: 1,
|
|
1271
1312
|
collect: ({ state, baselinePayload, memo }) => withLayoutProvenance(
|
|
1272
|
-
collectCoreConfigPayload(state.config) as Record<string, unknown>,
|
|
1313
|
+
collectCoreConfigPayload(publicConfig(state.config, localOnlyScope(state.config))) as Record<string, unknown>,
|
|
1273
1314
|
baselinePayload,
|
|
1274
1315
|
memo?.get(),
|
|
1275
1316
|
),
|
|
@@ -1282,7 +1323,7 @@ export const coreConfigSyncContributor: SyncContributor = {
|
|
|
1282
1323
|
const nextConfig = mergeConfigPayload(state.config, payload, baselineState.config, baselinePayload, {
|
|
1283
1324
|
memo: memo?.get(),
|
|
1284
1325
|
pushedAt,
|
|
1285
|
-
});
|
|
1326
|
+
}, localOnlyScope(state.config));
|
|
1286
1327
|
if (!nextConfig) return;
|
|
1287
1328
|
const configChanged = !valuesEqual(nextConfig, state.config);
|
|
1288
1329
|
if (configChanged) {
|
|
@@ -1328,15 +1369,24 @@ export const coreConfigSyncContributor: SyncContributor = {
|
|
|
1328
1369
|
export const coreCollectionsSyncContributor: SyncContributor = {
|
|
1329
1370
|
id: "core.collections",
|
|
1330
1371
|
schemaVersion: 1,
|
|
1331
|
-
collect: async ({ state }) =>
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
state.
|
|
1335
|
-
|
|
1336
|
-
state.
|
|
1337
|
-
|
|
1372
|
+
collect: async ({ state, isLocalMarketData }) => {
|
|
1373
|
+
// Connections the user keeps on this device stay here, with their portfolios, account values and
|
|
1374
|
+
// positions; so do the quotes and bars of a source that keeps its market data local.
|
|
1375
|
+
const scope = localOnlyScope(state.config);
|
|
1376
|
+
const config = publicConfig(state.config, scope);
|
|
1377
|
+
const tickers = publicTickers(state.tickers, scope);
|
|
1378
|
+
return collectCoreCollectionsPayload(
|
|
1379
|
+
config,
|
|
1380
|
+
tickers,
|
|
1381
|
+
state.financials,
|
|
1382
|
+
await loadHoldingExchangeRates(config, tickers, state.financials),
|
|
1383
|
+
state.brokerAccounts,
|
|
1384
|
+
publicMarketData(state.financials, isLocalMarketData),
|
|
1385
|
+
);
|
|
1386
|
+
},
|
|
1338
1387
|
apply: async (payload, { snapshot, baselinePayload, baselineState, getState, isCurrent, dispatch, tickerRepository }) => {
|
|
1339
1388
|
if (!isRecord(payload)) return;
|
|
1389
|
+
const scope = localOnlyScope(getState().config);
|
|
1340
1390
|
hydrateProfileAnalytics(payload);
|
|
1341
1391
|
if (!isCurrent()) return;
|
|
1342
1392
|
// The config contributor applies first, so this is the config this pull left.
|
|
@@ -1356,6 +1406,7 @@ export const coreCollectionsSyncContributor: SyncContributor = {
|
|
|
1356
1406
|
portfolios: [pulledPortfolios, pulledConfig?.portfolios].flatMap(idEntries),
|
|
1357
1407
|
brokerInstances: idEntries(pulledConfig?.brokerInstances),
|
|
1358
1408
|
}),
|
|
1409
|
+
localOnly: scope,
|
|
1359
1410
|
};
|
|
1360
1411
|
// Tickers here and pulled ones drop the ids of any watchlist that is gone
|
|
1361
1412
|
// (removed elsewhere, or deleted by an older build that left its id on the
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AppConfig, BrokerInstanceConfig } from "../types/config";
|
|
2
|
+
import type { Quote, TickerFinancials } from "../types/financials";
|
|
3
|
+
import type { BrokerContractRef } from "../types/instrument";
|
|
4
|
+
import type { Portfolio, TickerPosition, TickerRecord } from "../types/ticker";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Broker connections the user keeps on this device (`BrokerInstanceConfig.localOnly`), and the
|
|
8
|
+
* portfolios of their accounts. Cloud sync leaves the connections, those portfolios with their
|
|
9
|
+
* account values, and the positions held in them out of every push, and keeps them through every
|
|
10
|
+
* pull, so nothing of them reaches Gloom Cloud and a pull never removes them here.
|
|
11
|
+
*/
|
|
12
|
+
export interface LocalOnlyScope {
|
|
13
|
+
instanceIds: ReadonlySet<string>;
|
|
14
|
+
portfolioIds: ReadonlySet<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether a source keeps the quotes and bars it supplies on this device (`BrokerAdapter.localMarketData`),
|
|
19
|
+
* by its provider id: a broker's is its broker id.
|
|
20
|
+
*/
|
|
21
|
+
export type LocalMarketDataLookup = (providerId: string) => boolean;
|
|
22
|
+
|
|
23
|
+
/** The connections the user keeps on this device, or null when there are none. */
|
|
24
|
+
export function localOnlyScope(config: Pick<AppConfig, "brokerInstances" | "portfolios">): LocalOnlyScope | null {
|
|
25
|
+
const instanceIds = new Set(config.brokerInstances.flatMap((instance) => instance.localOnly === true ? [instance.id] : []));
|
|
26
|
+
if (instanceIds.size === 0) return null;
|
|
27
|
+
const scope = { instanceIds, portfolioIds: new Set<string>() };
|
|
28
|
+
return { instanceIds, portfolioIds: new Set(config.portfolios.flatMap((portfolio) => isLocalOnlyPortfolio(portfolio, scope) ? [portfolio.id] : [])) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isLocalOnlyInstance(instance: Pick<BrokerInstanceConfig, "id">, scope: LocalOnlyScope): boolean {
|
|
32
|
+
return scope.instanceIds.has(instance.id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isLocalOnlyPortfolio(portfolio: Pick<Portfolio, "id" | "brokerInstanceId">, scope: LocalOnlyScope): boolean {
|
|
36
|
+
return scope.portfolioIds.has(portfolio.id) || !!portfolio.brokerInstanceId && scope.instanceIds.has(portfolio.brokerInstanceId)
|
|
37
|
+
// A pulled portfolio has no profile id; its own id names the profile.
|
|
38
|
+
|| [...scope.instanceIds].some((id) => portfolio.id.startsWith(`broker:${id}:`));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isLocalOnlyPosition(position: Pick<TickerPosition, "portfolio" | "brokerInstanceId">, scope: LocalOnlyScope): boolean {
|
|
42
|
+
return scope.portfolioIds.has(position.portfolio) || !!position.brokerInstanceId && scope.instanceIds.has(position.brokerInstanceId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isLocalOnlyContract(contract: Pick<BrokerContractRef, "brokerInstanceId">, scope: LocalOnlyScope): boolean {
|
|
46
|
+
return !!contract.brokerInstanceId && scope.instanceIds.has(contract.brokerInstanceId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The config as Cloud sync may see it: without the connections kept on this device and their portfolios. */
|
|
50
|
+
export function publicConfig<T extends Pick<AppConfig, "brokerInstances" | "portfolios">>(config: T, scope: LocalOnlyScope | null): T {
|
|
51
|
+
if (!scope) return config;
|
|
52
|
+
const brokerInstances = config.brokerInstances.filter((instance) => !isLocalOnlyInstance(instance, scope));
|
|
53
|
+
const portfolios = config.portfolios.filter((portfolio) => !isLocalOnlyPortfolio(portfolio, scope));
|
|
54
|
+
if (brokerInstances.length === config.brokerInstances.length && portfolios.length === config.portfolios.length) return config;
|
|
55
|
+
return { ...config, brokerInstances, portfolios };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The ticker as Cloud sync may see it: without positions, portfolios or contracts of connections kept on this device. */
|
|
59
|
+
function publicTicker(ticker: TickerRecord, scope: LocalOnlyScope | null): TickerRecord {
|
|
60
|
+
if (!scope) return ticker;
|
|
61
|
+
const { metadata } = ticker;
|
|
62
|
+
const positions = metadata.positions.filter((position) => !isLocalOnlyPosition(position, scope));
|
|
63
|
+
const portfolios = metadata.portfolios.filter((id) => !scope.portfolioIds.has(id));
|
|
64
|
+
const contracts = metadata.broker_contracts?.filter((contract) => !isLocalOnlyContract(contract, scope));
|
|
65
|
+
if (positions.length === metadata.positions.length && portfolios.length === metadata.portfolios.length
|
|
66
|
+
&& contracts?.length === metadata.broker_contracts?.length) return ticker;
|
|
67
|
+
return { ...ticker, metadata: { ...metadata, positions, portfolios, ...(contracts ? { broker_contracts: contracts } : {}) } };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function publicTickers(tickers: Map<string, TickerRecord>, scope: LocalOnlyScope | null): Map<string, TickerRecord> {
|
|
71
|
+
if (!scope) return tickers;
|
|
72
|
+
return new Map([...tickers].map(([symbol, ticker]) => [symbol, publicTicker(ticker, scope)]));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The quote fields sync uploads, any of which a source that keeps its market data local may have supplied. */
|
|
76
|
+
const SYNCED_QUOTE_FIELDS = ["price", "change", "changePercent", "previousClose"] as const;
|
|
77
|
+
|
|
78
|
+
function isLocalQuote(quote: Quote, isLocal: LocalMarketDataLookup): boolean {
|
|
79
|
+
const provenance = quote.provenance;
|
|
80
|
+
const providers = [quote.providerId, provenance?.price?.providerId, ...SYNCED_QUOTE_FIELDS.map((field) => provenance?.fields?.[field]?.providerId)];
|
|
81
|
+
return providers.some((providerId) => !!providerId && isLocal(providerId));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The provider of stored bars, from their source key: `provider:<id>` or `broker:<broker id>:<profile>`. */
|
|
85
|
+
function historyProvider(sourceKey: string | undefined): string | null {
|
|
86
|
+
const [kind, id] = sourceKey?.split(":") ?? [];
|
|
87
|
+
return (kind === "provider" || kind === "broker") && id ? id : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Financials as Cloud sync may upload them: without a quote or bars a source that keeps its market
|
|
92
|
+
* data local supplied. Sync reads only the quote and the week's move from them.
|
|
93
|
+
*/
|
|
94
|
+
export function publicMarketData(financials: Map<string, TickerFinancials>, isLocal?: LocalMarketDataLookup): Map<string, TickerFinancials> {
|
|
95
|
+
if (!isLocal) return financials;
|
|
96
|
+
const output = new Map<string, TickerFinancials>();
|
|
97
|
+
for (const [symbol, entry] of financials) {
|
|
98
|
+
const localQuote = !!entry.quote && isLocalQuote(entry.quote, isLocal);
|
|
99
|
+
const provider = historyProvider(entry.priceHistorySourceKey);
|
|
100
|
+
const localBars = !!provider && isLocal(provider);
|
|
101
|
+
output.set(symbol, localQuote || localBars ? { ...entry, ...(localQuote ? { quote: undefined } : {}), ...(localBars ? { priceHistory: [] } : {}) } : entry);
|
|
102
|
+
}
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* `merged` (a list merged without the local-only entries) with this device's local-only entries
|
|
108
|
+
* of `local` back where they were: each stays at its place among the others, so an unchanged list
|
|
109
|
+
* comes back equal to `local`. An entry the pull brought that is local-only here is left out.
|
|
110
|
+
*/
|
|
111
|
+
export function withLocalOnlyEntries<T>(merged: readonly T[], local: readonly T[], isLocalOnly: (entry: T) => boolean): T[] {
|
|
112
|
+
const incoming = merged.filter((entry) => !isLocalOnly(entry));
|
|
113
|
+
const output: T[] = [];
|
|
114
|
+
let next = 0;
|
|
115
|
+
for (const entry of local) {
|
|
116
|
+
if (isLocalOnly(entry)) output.push(entry);
|
|
117
|
+
else if (next < incoming.length) output.push(incoming[next++]!);
|
|
118
|
+
}
|
|
119
|
+
output.push(...incoming.slice(next));
|
|
120
|
+
return output;
|
|
121
|
+
}
|
package/src/sync/react.ts
CHANGED
|
@@ -41,6 +41,12 @@ export function useCloudSyncRuntime({
|
|
|
41
41
|
baselineStore,
|
|
42
42
|
getContributors: () => pluginRegistry.getEnabledSyncContributors(),
|
|
43
43
|
getTransport: () => pluginRegistry.getActiveSyncTransport(),
|
|
44
|
+
isLocalMarketData: (providerId) => {
|
|
45
|
+
const broker = pluginRegistry.brokers.get(providerId);
|
|
46
|
+
if (broker) return broker.localMarketData === true;
|
|
47
|
+
// A broker whose plugin is off may still have its quotes about from before; they stay here too.
|
|
48
|
+
return getState().config.brokerInstances.some((instance) => instance.brokerType === providerId);
|
|
49
|
+
},
|
|
44
50
|
});
|
|
45
51
|
}, [baselineStore, dispatch, getState, pluginRegistry, tickerRepository]);
|
|
46
52
|
|
package/src/sync/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Dispatch } from "react";
|
|
2
2
|
import type { AppAction, AppState } from "../core/state/app/state";
|
|
3
3
|
import type { AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
4
|
+
import type { LocalMarketDataLookup } from "./local-only";
|
|
4
5
|
|
|
5
6
|
export const SYNC_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
6
7
|
|
|
@@ -31,6 +32,8 @@ interface SyncContributorMemo {
|
|
|
31
32
|
|
|
32
33
|
interface SyncCollectContext {
|
|
33
34
|
state: AppState;
|
|
35
|
+
/** Sources whose quotes and bars never leave this device (`BrokerAdapter.localMarketData`); none when absent. */
|
|
36
|
+
isLocalMarketData?: LocalMarketDataLookup;
|
|
34
37
|
/** The payload this device last pushed for this contributor, or null when it never synced. */
|
|
35
38
|
baselinePayload?: unknown;
|
|
36
39
|
memo?: SyncContributorMemo;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type CrashReportKind,
|
|
6
6
|
type CrashReportSurface,
|
|
7
7
|
type CrashReportsPayload,
|
|
8
|
+
type UpdateFailureReport,
|
|
8
9
|
} from "../api-client";
|
|
9
10
|
import type { TelemetryConfig } from "../types/config";
|
|
10
11
|
import { isSleepRpcTimeout } from "../utils/rpc-timeout-error";
|
|
@@ -68,6 +69,8 @@ export interface CrashReportContext {
|
|
|
68
69
|
pane?: string;
|
|
69
70
|
/** React's component stack for a render crash; appended to the stack. */
|
|
70
71
|
componentStack?: string | null;
|
|
72
|
+
/** A failed update step (update-failures.ts), sent as it is: it holds no free text. */
|
|
73
|
+
update?: UpdateFailureReport;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
interface PendingError {
|
|
@@ -167,6 +170,7 @@ function toReportError(error: unknown, context: CrashReportContext): PendingErro
|
|
|
167
170
|
...(stack ? { stack } : {}),
|
|
168
171
|
...(context.plugin ? { plugin: clip(context.plugin, MAX_LABEL_LENGTH) } : {}),
|
|
169
172
|
...(context.pane ? { pane: clip(context.pane, MAX_LABEL_LENGTH) } : {}),
|
|
173
|
+
...(context.update ? { update: context.update } : {}),
|
|
170
174
|
},
|
|
171
175
|
};
|
|
172
176
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { UpdateChannel } from "../types/config";
|
|
4
|
+
import type {
|
|
5
|
+
UpdateAttempt,
|
|
6
|
+
UpdateFailureReporterHost,
|
|
7
|
+
UpdateInstallKind,
|
|
8
|
+
UpdateReportState,
|
|
9
|
+
} from "./update-failures";
|
|
10
|
+
|
|
11
|
+
/** Next to config.json: when each update failure was last reported, and the last update attempt. */
|
|
12
|
+
const STATE_FILE = "update-reports.json";
|
|
13
|
+
|
|
14
|
+
function readAttempt(value: unknown): UpdateAttempt | undefined {
|
|
15
|
+
if (!value || typeof value !== "object") return undefined;
|
|
16
|
+
const { from, to, channel, at } = value as Record<string, unknown>;
|
|
17
|
+
if (typeof from !== "string" || typeof to !== "string" || typeof at !== "number" || !Number.isFinite(at)) return undefined;
|
|
18
|
+
return { from, to, channel: (channel === "nightly" ? "nightly" : "stable") satisfies UpdateChannel, at };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The saved state, keeping only entries of the right shape. */
|
|
22
|
+
function parseUpdateReportState(text: string): UpdateReportState {
|
|
23
|
+
const parsed: unknown = JSON.parse(text);
|
|
24
|
+
const record = parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
|
|
25
|
+
const sent: Record<string, number> = {};
|
|
26
|
+
if (record.sent && typeof record.sent === "object") {
|
|
27
|
+
for (const [key, at] of Object.entries(record.sent as Record<string, unknown>)) {
|
|
28
|
+
if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const staged = readAttempt(record.staged);
|
|
32
|
+
const applying = readAttempt(record.applying);
|
|
33
|
+
return { sent, ...(staged ? { staged } : {}), ...(applying ? { applying } : {}) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The update failure reporter for a Bun process (the terminal app, the desktop app's main process). */
|
|
37
|
+
export function createNodeUpdateFailureHost(options: {
|
|
38
|
+
installKind: UpdateInstallKind;
|
|
39
|
+
/** The profile's data folder, or null before the config is loaded. */
|
|
40
|
+
getDataDir(): string | null;
|
|
41
|
+
}): UpdateFailureReporterHost {
|
|
42
|
+
return {
|
|
43
|
+
installKind: options.installKind,
|
|
44
|
+
platform: process.platform,
|
|
45
|
+
arch: process.arch,
|
|
46
|
+
load: () => {
|
|
47
|
+
const dir = options.getDataDir();
|
|
48
|
+
const path = dir ? join(dir, STATE_FILE) : null;
|
|
49
|
+
if (!path || !existsSync(path)) return { sent: {} };
|
|
50
|
+
return parseUpdateReportState(readFileSync(path, "utf-8"));
|
|
51
|
+
},
|
|
52
|
+
save: (state) => {
|
|
53
|
+
const dir = options.getDataDir();
|
|
54
|
+
if (!dir) return;
|
|
55
|
+
mkdirSync(dir, { recursive: true });
|
|
56
|
+
writeFileSync(join(dir, STATE_FILE), JSON.stringify(state), "utf-8");
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|