gloomberb 0.9.7 → 0.9.9
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/src/api-client/auth.ts +20 -0
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +48 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +45 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +52 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -10
- package/src/market-data/coordinator/index.ts +41 -21
- package/src/market-data/coordinator/quotes.ts +13 -11
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +96 -10
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -8
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +175 -59
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +259 -23
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +127 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +153 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +22 -12
- package/src/plugins/builtin/cloud/plugin.tsx +8 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +75 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +77 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +314 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +19 -10
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +300 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +15 -1
- package/src/ui/index.tsx +2 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -27,6 +27,12 @@ import type {
|
|
|
27
27
|
} from "../../types/plugin";
|
|
28
28
|
import type { ContextMenuContext, ContextMenuItem } from "../../types/context-menu";
|
|
29
29
|
import type { TickerRecord } from "../../types/ticker";
|
|
30
|
+
import type {
|
|
31
|
+
RegisteredSyncContributor,
|
|
32
|
+
RegisteredSyncTransport,
|
|
33
|
+
SyncContributor,
|
|
34
|
+
SyncTransport,
|
|
35
|
+
} from "../../sync/types";
|
|
30
36
|
import { EventBus } from "../event-bus";
|
|
31
37
|
import { resolvePaneInstance } from "../../types/config";
|
|
32
38
|
import { debugLog } from "../../utils/debug-log";
|
|
@@ -49,6 +55,7 @@ import {
|
|
|
49
55
|
releaseSharedRegistry,
|
|
50
56
|
} from "./shared";
|
|
51
57
|
import { RegistryResumeStateListeners } from "./plugin-state";
|
|
58
|
+
import { cloudSyncController } from "../../sync/controller";
|
|
52
59
|
|
|
53
60
|
interface PluginRegistryOptions {
|
|
54
61
|
enableCapabilityHandlers?: boolean;
|
|
@@ -215,6 +222,28 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
215
222
|
get tickerActions(): ReadonlyMap<string, TickerAction> { return this.contributions.tickerActionsMap; }
|
|
216
223
|
get allPlugins(): ReadonlyMap<string, GloomPlugin> { return this.plugins; }
|
|
217
224
|
|
|
225
|
+
registerSyncContributorForPlugin(pluginId: string, contributor: SyncContributor): () => void {
|
|
226
|
+
return cloudSyncController.registerContributor(pluginId, contributor);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
registerSyncTransportForPlugin(pluginId: string, transport: SyncTransport): () => void {
|
|
230
|
+
return cloudSyncController.registerTransport(pluginId, transport);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getEnabledSyncContributors(): RegisteredSyncContributor[] {
|
|
234
|
+
const disabledPlugins = new Set(this.getConfigFn().disabledPlugins ?? []);
|
|
235
|
+
return cloudSyncController
|
|
236
|
+
.getRegisteredContributors()
|
|
237
|
+
.filter((entry) => !disabledPlugins.has(entry.pluginId));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getActiveSyncTransport(): RegisteredSyncTransport | null {
|
|
241
|
+
const disabledPlugins = new Set(this.getConfigFn().disabledPlugins ?? []);
|
|
242
|
+
return cloudSyncController
|
|
243
|
+
.getRegisteredTransports()
|
|
244
|
+
.find((entry) => !disabledPlugins.has(entry.pluginId) && entry.transport.isAvailable()) ?? null;
|
|
245
|
+
}
|
|
246
|
+
|
|
218
247
|
getContextMenuItems(context: ContextMenuContext): ContextMenuItem[] {
|
|
219
248
|
return resolveRegistryContextMenuItems({
|
|
220
249
|
context,
|
|
@@ -373,6 +402,8 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
373
402
|
updateLayout: (layout) => this.updateLayoutFn(layout),
|
|
374
403
|
resolvePaneTarget: (paneId) => this.resolvePaneTarget(paneId),
|
|
375
404
|
registerCapabilityForPlugin: (targetPluginId, capability, pluginItems) => this.registerCapabilityForPlugin(targetPluginId, capability, pluginItems),
|
|
405
|
+
registerSyncContributorForPlugin: (targetPluginId, contributor) => this.registerSyncContributorForPlugin(targetPluginId, contributor),
|
|
406
|
+
registerSyncTransportForPlugin: (targetPluginId, transport) => this.registerSyncTransportForPlugin(targetPluginId, transport),
|
|
376
407
|
watchNewsQuery: (query, listener) => this.watchNewsQueryFn(query, listener),
|
|
377
408
|
getData: (ticker) => this.getDataFn(ticker),
|
|
378
409
|
getTicker: (symbol) => this.getTickerFn(symbol),
|
|
@@ -410,35 +441,47 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
410
441
|
|
|
411
442
|
async register(plugin: GloomPlugin): Promise<void> {
|
|
412
443
|
this.registryLog.info(`Registering plugin: ${plugin.id} v${plugin.version ?? "?"}`);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
444
|
+
if (this.plugins.has(plugin.id)) throw new Error(`Plugin already registered: ${plugin.id}`);
|
|
445
|
+
this.plugins.set(plugin.id, plugin);
|
|
446
|
+
try {
|
|
447
|
+
const items = this.contributions.getOrCreatePluginItems(plugin.id);
|
|
448
|
+
if (plugin.panes) {
|
|
449
|
+
for (const pane of plugin.panes) {
|
|
450
|
+
this.contributions.registerPane(plugin.id, pane, items);
|
|
451
|
+
}
|
|
417
452
|
}
|
|
418
|
-
}
|
|
419
453
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
454
|
+
if (plugin.paneTemplates) {
|
|
455
|
+
for (const template of plugin.paneTemplates) {
|
|
456
|
+
this.contributions.registerPaneTemplate(plugin.id, template, items);
|
|
457
|
+
}
|
|
423
458
|
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (plugin.broker) {
|
|
427
|
-
this.contributions.registerBroker(plugin.id, plugin.broker, items);
|
|
428
|
-
}
|
|
429
459
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
this.registerCapabilityForPlugin(plugin.id, capability, items);
|
|
460
|
+
if (plugin.broker) {
|
|
461
|
+
this.contributions.registerBroker(plugin.id, plugin.broker, items);
|
|
433
462
|
}
|
|
434
|
-
}
|
|
435
463
|
|
|
436
|
-
|
|
464
|
+
if (this.enableCapabilityHandlers && plugin.capabilities) {
|
|
465
|
+
for (const capability of plugin.capabilities) {
|
|
466
|
+
this.registerCapabilityForPlugin(plugin.id, capability, items);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
437
469
|
|
|
438
|
-
|
|
470
|
+
this.slots.register(plugin, this);
|
|
439
471
|
|
|
440
|
-
|
|
441
|
-
|
|
472
|
+
if (plugin.setup) {
|
|
473
|
+
await plugin.setup(this.createContext(plugin.id));
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
try {
|
|
477
|
+
this.removePlugin(plugin.id);
|
|
478
|
+
} catch (cleanupError) {
|
|
479
|
+
this.registryLog.error("Failed to clean up rejected plugin registration", {
|
|
480
|
+
pluginId: plugin.id,
|
|
481
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
throw error;
|
|
442
485
|
}
|
|
443
486
|
|
|
444
487
|
this.events.emit("plugin:registered", { pluginId: plugin.id });
|
|
@@ -449,13 +492,19 @@ export class PluginRegistry implements PluginRuntimeAccess {
|
|
|
449
492
|
if (!plugin) return;
|
|
450
493
|
this.registryLog.info(`Unregistering plugin: ${pluginId}`);
|
|
451
494
|
|
|
452
|
-
|
|
453
|
-
this.
|
|
454
|
-
|
|
455
|
-
this.contributions.unregister(pluginId);
|
|
495
|
+
this.removePlugin(pluginId);
|
|
496
|
+
this.events.emit("plugin:unregistered", { pluginId });
|
|
497
|
+
}
|
|
456
498
|
|
|
499
|
+
private removePlugin(pluginId: string): void {
|
|
500
|
+
const plugin = this.plugins.get(pluginId);
|
|
501
|
+
if (!plugin) return;
|
|
502
|
+
let cleanupError: unknown;
|
|
503
|
+
try { plugin.dispose?.(); } catch (error) { cleanupError = error; }
|
|
504
|
+
try { this.slots.unregister(pluginId); } catch (error) { cleanupError ??= error; }
|
|
505
|
+
try { this.contributions.unregister(pluginId); } catch (error) { cleanupError ??= error; }
|
|
457
506
|
this.plugins.delete(pluginId);
|
|
458
|
-
|
|
507
|
+
if (cleanupError) throw cleanupError;
|
|
459
508
|
}
|
|
460
509
|
|
|
461
510
|
destroy(): void {
|
package/src/remote/controller.ts
CHANGED
|
@@ -51,7 +51,6 @@ interface AppRemoteControllerOptions {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const DEFAULT_MUTATION_INCLUDE: RemoteStateInclude[] = ["app", "layout", "panes", "commandBar"];
|
|
54
|
-
const DEFAULT_BATCH_INCLUDE: RemoteStateInclude[] = ["app", "layout", "panes", "commandBar"];
|
|
55
54
|
|
|
56
55
|
export function createAppRemoteController({
|
|
57
56
|
dispatch,
|
|
@@ -68,11 +67,6 @@ export function createAppRemoteController({
|
|
|
68
67
|
uiRegistry,
|
|
69
68
|
});
|
|
70
69
|
|
|
71
|
-
const getAfterMutation = async (resource: string): Promise<unknown> => {
|
|
72
|
-
await afterMutation();
|
|
73
|
-
return getResource(resource);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
70
|
const getAfterMutationSummary = async (extra?: Record<string, unknown>): Promise<unknown> => {
|
|
77
71
|
await afterMutation();
|
|
78
72
|
return mutationSummary(getState(), extra);
|
|
@@ -420,7 +414,7 @@ export function createAppRemoteController({
|
|
|
420
414
|
ok: responses.every((response) => response.ok),
|
|
421
415
|
haltedAt,
|
|
422
416
|
responses,
|
|
423
|
-
}, undefined, buildIncludedState(request.include, request.dryRun ? [] :
|
|
417
|
+
}, undefined, buildIncludedState(request.include, request.dryRun ? [] : DEFAULT_MUTATION_INCLUDE));
|
|
424
418
|
}
|
|
425
419
|
default:
|
|
426
420
|
throw new Error(`Unknown remote request type: ${(request as { type?: unknown }).type}`);
|
package/src/remote/json-patch.ts
CHANGED
|
@@ -12,6 +12,14 @@ function pointerSegments(path: string): string[] {
|
|
|
12
12
|
return path.slice(1).split("/").map(decodePointerSegment);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const UNSAFE_OBJECT_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
16
|
+
|
|
17
|
+
function assertSafeObjectKey(key: string): void {
|
|
18
|
+
if (UNSAFE_OBJECT_KEYS.has(key)) {
|
|
19
|
+
throw new Error(`Unsafe object key in JSON patch path: ${key}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
function cloneJson<T>(value: T): T {
|
|
16
24
|
return value == null ? value : JSON.parse(JSON.stringify(value)) as T;
|
|
17
25
|
}
|
|
@@ -30,12 +38,18 @@ function containerFor(root: unknown, path: string): { parent: unknown; key: stri
|
|
|
30
38
|
}
|
|
31
39
|
parent = parent[index];
|
|
32
40
|
} else if (parent && typeof parent === "object") {
|
|
41
|
+
assertSafeObjectKey(segment);
|
|
42
|
+
if (!Object.prototype.hasOwnProperty.call(parent, segment)) {
|
|
43
|
+
throw new Error(`Cannot descend into missing object key: ${segment}`);
|
|
44
|
+
}
|
|
33
45
|
parent = (parent as Record<string, unknown>)[segment];
|
|
34
46
|
} else {
|
|
35
47
|
throw new Error(`Cannot descend into non-object patch path: ${path}`);
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
|
-
|
|
50
|
+
const key = segments[segments.length - 1]!;
|
|
51
|
+
if (!Array.isArray(parent)) assertSafeObjectKey(key);
|
|
52
|
+
return { parent, key };
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
function applyOperation(root: unknown, operation: RemoteJsonPatchOperation): void {
|
package/src/remote/server.ts
CHANGED
|
@@ -45,21 +45,31 @@ export async function startRemoteControlServer(options: RemoteControlServerOptio
|
|
|
45
45
|
token,
|
|
46
46
|
startedAt: new Date().toISOString(),
|
|
47
47
|
};
|
|
48
|
-
|
|
48
|
+
try {
|
|
49
|
+
await writeRemoteEndpointFiles(options.dataDir, endpoint);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
await closeHttpServer(httpServer);
|
|
52
|
+
await removeRemoteEndpointFiles(options.dataDir, endpoint).catch(() => {});
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
49
55
|
|
|
50
56
|
return {
|
|
51
57
|
endpoint,
|
|
52
58
|
close: async () => {
|
|
53
|
-
await
|
|
54
|
-
httpServer.close((error) => (error ? reject(error) : resolve()));
|
|
55
|
-
}).catch((error) => {
|
|
56
|
-
if ((error as NodeJS.ErrnoException).code !== "ERR_SERVER_NOT_RUNNING") throw error;
|
|
57
|
-
});
|
|
59
|
+
await closeHttpServer(httpServer);
|
|
58
60
|
await removeRemoteEndpointFiles(options.dataDir, endpoint);
|
|
59
61
|
},
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
async function closeHttpServer(httpServer: ReturnType<typeof createServer>): Promise<void> {
|
|
66
|
+
await new Promise<void>((resolve, reject) => {
|
|
67
|
+
httpServer.close((error) => (error ? reject(error) : resolve()));
|
|
68
|
+
}).catch((error) => {
|
|
69
|
+
if ((error as NodeJS.ErrnoException).code !== "ERR_SERVER_NOT_RUNNING") throw error;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
async function handleHttpRequest(
|
|
64
74
|
handle: RemoteControlServerOptions["handle"],
|
|
65
75
|
token: string,
|
|
@@ -26,7 +26,7 @@ function openCommandBar(label: string, query: string, options?: { accelerator?:
|
|
|
26
26
|
return commandItem(label, { type: "open-command-bar", query }, options);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
function buildApplicationMenu(): ApplicationMenuItemConfig[] {
|
|
30
30
|
return [
|
|
31
31
|
{
|
|
32
32
|
label: "Gloomberb",
|
|
@@ -19,6 +19,7 @@ const NOTES_INDEX_FILE = "__quick-notes-index__.json";
|
|
|
19
19
|
const BROKER_INVOKE_OPERATIONS = new Set([
|
|
20
20
|
"validate",
|
|
21
21
|
"importPositions",
|
|
22
|
+
"importPortfolioSnapshot",
|
|
22
23
|
"connect",
|
|
23
24
|
"disconnect",
|
|
24
25
|
"getPersistedConfigUpdate",
|
|
@@ -96,6 +97,7 @@ function invokeBrokerOperation(
|
|
|
96
97
|
switch (operation) {
|
|
97
98
|
case "validate":
|
|
98
99
|
case "importPositions":
|
|
100
|
+
case "importPortfolioSnapshot":
|
|
99
101
|
case "connect":
|
|
100
102
|
case "disconnect":
|
|
101
103
|
case "getPersistedConfigUpdate":
|
|
@@ -39,4 +39,18 @@ describe("handleHttpFetch", () => {
|
|
|
39
39
|
expect(response.headers.location).toBe("/reader");
|
|
40
40
|
expect(response.setCookie).toEqual(["substack.sid=sid123; Path=/; HttpOnly"]);
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
test("aborts the backend fetch at the requested deadline", async () => {
|
|
44
|
+
server = Bun.serve({
|
|
45
|
+
port: 0,
|
|
46
|
+
fetch() {
|
|
47
|
+
return new Promise<Response>(() => {});
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await expect(handleHttpFetch({
|
|
52
|
+
url: server.url.toString(),
|
|
53
|
+
init: { timeoutMs: 10 },
|
|
54
|
+
})).rejects.toThrow();
|
|
55
|
+
});
|
|
42
56
|
});
|
|
@@ -34,12 +34,20 @@ export async function handleHttpFetch(payload: Record<string, unknown>) {
|
|
|
34
34
|
typeof init.body === "string" && method !== "GET" && method !== "HEAD"
|
|
35
35
|
? init.body
|
|
36
36
|
: undefined;
|
|
37
|
+
const timeoutMs =
|
|
38
|
+
typeof init.timeoutMs === "number"
|
|
39
|
+
&& Number.isFinite(init.timeoutMs)
|
|
40
|
+
&& init.timeoutMs > 0
|
|
41
|
+
&& init.timeoutMs <= 120_000
|
|
42
|
+
? init.timeoutMs
|
|
43
|
+
: undefined;
|
|
37
44
|
|
|
38
45
|
const response = await fetch(url, {
|
|
39
46
|
method,
|
|
40
47
|
headers: normalizeHttpFetchHeaders(init.headers),
|
|
41
48
|
body,
|
|
42
49
|
redirect,
|
|
50
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,
|
|
43
51
|
});
|
|
44
52
|
const responseHeaders: Record<string, string> = {};
|
|
45
53
|
response.headers.forEach((value, key) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { createAppServices, type AppServices } from "../../../../core/app-services";
|
|
4
5
|
import type { AppSessionSnapshot } from "../../../../core/state/session-persistence";
|
|
@@ -98,7 +99,7 @@ function buildInitializationPayload(
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
async function resolveDesktopDataDir(): Promise<string> {
|
|
101
|
-
const dataDir = await getDataDir() ?? join(process.env.HOME ||
|
|
102
|
+
const dataDir = await getDataDir() ?? join(process.env.HOME || homedir(), ".gloomberb");
|
|
102
103
|
if (!existsSync(dataDir)) {
|
|
103
104
|
mkdirSync(dataDir, { recursive: true });
|
|
104
105
|
}
|
|
@@ -103,6 +103,41 @@ function requireDesktopWorkspace(): DesktopWorkspace {
|
|
|
103
103
|
return desktopWorkspace;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
const pendingDesktopDeepLinks: string[] = [];
|
|
107
|
+
|
|
108
|
+
function isGloomberbDeepLink(rawUrl: string): boolean {
|
|
109
|
+
try {
|
|
110
|
+
return new URL(rawUrl).protocol === "gloomberb:";
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readOpenUrlEvent(event: unknown): string | null {
|
|
117
|
+
const data = event && typeof event === "object" ? (event as { data?: unknown }).data : null;
|
|
118
|
+
if (!data || typeof data !== "object") return null;
|
|
119
|
+
const url = (data as { url?: unknown }).url;
|
|
120
|
+
return typeof url === "string" && url ? url : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sendDesktopDeepLink(rawUrl: string): void {
|
|
124
|
+
if (!isGloomberbDeepLink(rawUrl)) return;
|
|
125
|
+
const rpc = getWindowRpc(MAIN_WINDOW_RPC_KEY);
|
|
126
|
+
if (!rpc || !isWindowRpcReady(MAIN_WINDOW_RPC_KEY)) {
|
|
127
|
+
pendingDesktopDeepLinks.push(rawUrl);
|
|
128
|
+
if (pendingDesktopDeepLinks.length > 20) pendingDesktopDeepLinks.shift();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
detachedWindowManager.focusWindowForRpcKey(MAIN_WINDOW_RPC_KEY);
|
|
132
|
+
rpc.send["desktop.deepLink"]({ url: rawUrl });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function flushPendingDesktopDeepLinks(): void {
|
|
136
|
+
if (pendingDesktopDeepLinks.length === 0) return;
|
|
137
|
+
const urls = pendingDesktopDeepLinks.splice(0);
|
|
138
|
+
for (const url of urls) sendDesktopDeepLink(url);
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
function remoteFailure(code: string, message: string): RemoteControlResponse {
|
|
107
142
|
return { ok: false, error: { code, message } };
|
|
108
143
|
}
|
|
@@ -377,6 +412,7 @@ async function initialize(
|
|
|
377
412
|
syncConfigAccessors,
|
|
378
413
|
});
|
|
379
414
|
if (init.windowKind === "main") {
|
|
415
|
+
flushPendingDesktopDeepLinks();
|
|
380
416
|
void ensureDesktopRemoteControlServer().catch((error) => {
|
|
381
417
|
console.error("[remote] desktop control endpoint failed", summarizeError(error));
|
|
382
418
|
});
|
|
@@ -502,6 +538,12 @@ Electrobun.events.on("context-menu-clicked", (event: unknown) => {
|
|
|
502
538
|
});
|
|
503
539
|
});
|
|
504
540
|
|
|
541
|
+
Electrobun.events.on("open-url", (event: unknown) => {
|
|
542
|
+
const url = readOpenUrlEvent(event);
|
|
543
|
+
if (!url) return;
|
|
544
|
+
sendDesktopDeepLink(url);
|
|
545
|
+
});
|
|
546
|
+
|
|
505
547
|
ApplicationMenu.on("application-menu-clicked", (event: unknown) => {
|
|
506
548
|
const command = applicationMenuCommand(event);
|
|
507
549
|
if (!command) return;
|
|
@@ -60,6 +60,10 @@ export interface DesktopRestartMessage {
|
|
|
60
60
|
source?: string;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export interface DesktopDeepLinkMessage {
|
|
64
|
+
url: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
export interface RemoteControlRequestMessage {
|
|
64
68
|
request: RemoteControlRequest;
|
|
65
69
|
}
|
|
@@ -89,6 +93,7 @@ export interface ElectrobunDesktopRpcSchema {
|
|
|
89
93
|
"desktop.state": DesktopStateMessage;
|
|
90
94
|
"desktop.dockPreview": DesktopDockPreviewMessage;
|
|
91
95
|
"desktop.themePreview": DesktopThemePreviewMessage;
|
|
96
|
+
"desktop.deepLink": DesktopDeepLinkMessage;
|
|
92
97
|
"update.progress": UpdateProgressMessage;
|
|
93
98
|
"capability.event": CapabilityEventMessage;
|
|
94
99
|
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type ApplicationMenuSelectMessage,
|
|
5
5
|
type CapabilityEventMessage,
|
|
6
6
|
type ContextMenuSelectMessage,
|
|
7
|
+
type DesktopDeepLinkMessage,
|
|
7
8
|
type DesktopDockPreviewMessage,
|
|
8
9
|
type DesktopRestartMessage,
|
|
9
10
|
type DesktopStateMessage,
|
|
@@ -18,6 +19,7 @@ import type { RemoteControlRequest, RemoteControlResponse } from "../../../remot
|
|
|
18
19
|
|
|
19
20
|
type ContextMenuSelectListener = (message: ContextMenuSelectMessage) => void;
|
|
20
21
|
type ApplicationMenuSelectListener = (message: ApplicationMenuSelectMessage) => void;
|
|
22
|
+
type DesktopDeepLinkListener = (message: DesktopDeepLinkMessage) => void;
|
|
21
23
|
type DesktopStateListener = (message: DesktopStateMessage) => void;
|
|
22
24
|
type DesktopDockPreviewListener = (message: DesktopDockPreviewMessage) => void;
|
|
23
25
|
type DesktopThemePreviewListener = (message: DesktopThemePreviewMessage) => void;
|
|
@@ -29,6 +31,8 @@ let initSnapshot: ElectrobunBackendInit | null = null;
|
|
|
29
31
|
let remoteControlRequestHandler: RemoteControlRequestHandler | null = null;
|
|
30
32
|
const contextMenuSelectListeners = new Map<string, Set<ContextMenuSelectListener>>();
|
|
31
33
|
const applicationMenuSelectListeners = new Set<ApplicationMenuSelectListener>();
|
|
34
|
+
const desktopDeepLinkListeners = new Set<DesktopDeepLinkListener>();
|
|
35
|
+
const pendingDesktopDeepLinks: DesktopDeepLinkMessage[] = [];
|
|
32
36
|
const desktopStateListeners = new Set<DesktopStateListener>();
|
|
33
37
|
const desktopDockPreviewListeners = new Set<DesktopDockPreviewListener>();
|
|
34
38
|
const desktopThemePreviewListeners = new Set<DesktopThemePreviewListener>();
|
|
@@ -64,6 +68,17 @@ function subscribe<T>(
|
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
function dispatchDesktopDeepLink(message: DesktopDeepLinkMessage): void {
|
|
72
|
+
if (desktopDeepLinkListeners.size === 0) {
|
|
73
|
+
pendingDesktopDeepLinks.push(message);
|
|
74
|
+
if (pendingDesktopDeepLinks.length > 20) pendingDesktopDeepLinks.shift();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const listener of desktopDeepLinkListeners) {
|
|
78
|
+
listener(message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
const rpc = Electroview.defineRPC<ElectrobunDesktopRpcSchema>({
|
|
68
83
|
maxRequestTime: 120_000,
|
|
69
84
|
handlers: {
|
|
@@ -92,6 +107,10 @@ const rpc = Electroview.defineRPC<ElectrobunDesktopRpcSchema>({
|
|
|
92
107
|
listener(decoded);
|
|
93
108
|
}
|
|
94
109
|
},
|
|
110
|
+
"desktop.deepLink": (message) => {
|
|
111
|
+
if (typeof message.url !== "string" || !message.url) return;
|
|
112
|
+
dispatchDesktopDeepLink({ url: message.url });
|
|
113
|
+
},
|
|
95
114
|
"desktop.state": (message) => {
|
|
96
115
|
for (const listener of desktopStateListeners) {
|
|
97
116
|
listener({ snapshot: decodeRpcValue(message.snapshot) });
|
|
@@ -188,6 +207,16 @@ export function onApplicationMenuSelect(listener: ApplicationMenuSelectListener)
|
|
|
188
207
|
};
|
|
189
208
|
}
|
|
190
209
|
|
|
210
|
+
export function onDesktopDeepLink(listener: DesktopDeepLinkListener): () => void {
|
|
211
|
+
desktopDeepLinkListeners.add(listener);
|
|
212
|
+
for (const message of pendingDesktopDeepLinks.splice(0)) {
|
|
213
|
+
listener(message);
|
|
214
|
+
}
|
|
215
|
+
return () => {
|
|
216
|
+
desktopDeepLinkListeners.delete(listener);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
191
220
|
export function onDesktopState(listener: DesktopStateListener): () => void {
|
|
192
221
|
desktopStateListeners.add(listener);
|
|
193
222
|
return () => {
|
|
@@ -261,6 +261,7 @@ function createRuntime(payload: CliPaneShotPayload): PluginRuntimeAccess {
|
|
|
261
261
|
switchPanel: () => {},
|
|
262
262
|
openCommandBar: () => {},
|
|
263
263
|
showPane: () => {},
|
|
264
|
+
createPaneFromTemplate: () => {},
|
|
264
265
|
hidePane: () => {},
|
|
265
266
|
focusPane: () => {},
|
|
266
267
|
openPaneSettings: () => {},
|
|
@@ -19,7 +19,6 @@ export const CSS_TEXT_DIM = "var(--gloom-text-dim)";
|
|
|
19
19
|
export const CSS_TEXT_BRIGHT = "var(--gloom-text-bright)";
|
|
20
20
|
export const CSS_SELECTED = "var(--gloom-selected)";
|
|
21
21
|
export const CSS_SELECTED_TEXT = "var(--gloom-selected-text)";
|
|
22
|
-
export const CSS_HOVER_BG = "var(--gloom-hover-bg)";
|
|
23
22
|
|
|
24
23
|
function hasAttribute(attributes: unknown, flag: number): boolean {
|
|
25
24
|
return typeof attributes === "number" && (attributes & flag) !== 0;
|