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
package/package.json
CHANGED
package/src/api-client/auth.ts
CHANGED
|
@@ -38,6 +38,11 @@ export class CloudAuthApi {
|
|
|
38
38
|
username: typeof user.username === "string" ? user.username : null,
|
|
39
39
|
emailVerified: user.emailVerified === true,
|
|
40
40
|
image: typeof user.image === "string" ? user.image : null,
|
|
41
|
+
syncEnabled: user.syncEnabled === false ? false : true,
|
|
42
|
+
weeklyRoundupEnabled: user.weeklyRoundupEnabled === false ? false : true,
|
|
43
|
+
positionAlertsEnabled: user.positionAlertsEnabled === false ? false : true,
|
|
44
|
+
lastSyncAt: typeof user.lastSyncAt === "string" ? user.lastSyncAt : null,
|
|
45
|
+
lastRoundupEmailAt: typeof user.lastRoundupEmailAt === "string" ? user.lastRoundupEmailAt : null,
|
|
41
46
|
createdAt: typeof user.createdAt === "string" ? user.createdAt : "",
|
|
42
47
|
updatedAt: typeof user.updatedAt === "string" ? user.updatedAt : "",
|
|
43
48
|
});
|
|
@@ -148,6 +153,12 @@ export class CloudAuthApi {
|
|
|
148
153
|
xAccount: profile.xAccount,
|
|
149
154
|
sharedPortfolioId: profile.sharedPortfolioId,
|
|
150
155
|
acceptUnknownDms: profile.acceptUnknownDms,
|
|
156
|
+
portfolioAnalytics: profile.portfolioAnalytics,
|
|
157
|
+
syncEnabled: profile.syncEnabled,
|
|
158
|
+
weeklyRoundupEnabled: profile.weeklyRoundupEnabled,
|
|
159
|
+
positionAlertsEnabled: profile.positionAlertsEnabled,
|
|
160
|
+
lastSyncAt: profile.lastSyncAt,
|
|
161
|
+
lastRoundupEmailAt: profile.lastRoundupEmailAt,
|
|
151
162
|
updatedAt: profile.updatedAt ?? currentUser.updatedAt,
|
|
152
163
|
}));
|
|
153
164
|
}
|
|
@@ -164,4 +175,13 @@ export class CloudAuthApi {
|
|
|
164
175
|
}),
|
|
165
176
|
});
|
|
166
177
|
}
|
|
178
|
+
|
|
179
|
+
async deleteAccount(): Promise<void> {
|
|
180
|
+
await this.options.request("/account", {
|
|
181
|
+
method: "DELETE",
|
|
182
|
+
body: JSON.stringify({}),
|
|
183
|
+
});
|
|
184
|
+
this.options.setSessionToken(null);
|
|
185
|
+
this.options.setCurrentUser(null);
|
|
186
|
+
}
|
|
167
187
|
}
|
|
@@ -415,7 +415,7 @@ describe("apiClient chat timestamps", () => {
|
|
|
415
415
|
seenCreatedAts.push(message.createdAt);
|
|
416
416
|
});
|
|
417
417
|
|
|
418
|
-
await (apiClient as any).handleSocketMessage(JSON.stringify({
|
|
418
|
+
await (apiClient as any).socket.handleSocketMessage(JSON.stringify({
|
|
419
419
|
type: "chat.message",
|
|
420
420
|
channelId: "everyone",
|
|
421
421
|
data: {
|
|
@@ -531,11 +531,11 @@ describe("apiClient chat timestamps", () => {
|
|
|
531
531
|
seenNotifications.push(`${notification.id}:${notification.message.createdAt}`);
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
-
await (apiClient as any).handleSocketMessage(JSON.stringify({
|
|
534
|
+
await (apiClient as any).socket.handleSocketMessage(JSON.stringify({
|
|
535
535
|
type: "chat.presence",
|
|
536
536
|
onlineCount: 5,
|
|
537
537
|
}));
|
|
538
|
-
await (apiClient as any).handleSocketMessage(JSON.stringify({
|
|
538
|
+
await (apiClient as any).socket.handleSocketMessage(JSON.stringify({
|
|
539
539
|
type: "chat.notification",
|
|
540
540
|
data: {
|
|
541
541
|
id: "n1",
|
package/src/api-client/index.ts
CHANGED
|
@@ -39,15 +39,17 @@ import type {
|
|
|
39
39
|
CloudCongressHousePayload,
|
|
40
40
|
CloudNewsPayload,
|
|
41
41
|
CloudNewsListResponse,
|
|
42
|
-
CloudTweetPayload,
|
|
43
|
-
CloudTweetQueryType,
|
|
44
42
|
CloudTweetSearchResponse,
|
|
45
43
|
CloudMarketResponse,
|
|
46
44
|
CloudMarketBatchTarget,
|
|
47
45
|
CloudMarketBatchPayload,
|
|
48
46
|
CloudVerificationResponse,
|
|
47
|
+
CloudRoundupPreviewResponse,
|
|
48
|
+
CloudSyncPushResponse,
|
|
49
|
+
CloudSyncSnapshotResponse,
|
|
49
50
|
QuoteStreamTarget,
|
|
50
51
|
} from "./types";
|
|
52
|
+
import type { SyncSettings, SyncSnapshot } from "../sync/types";
|
|
51
53
|
|
|
52
54
|
export type * from "./types";
|
|
53
55
|
export { setCloudApiFetchTransport } from "./request";
|
|
@@ -154,10 +156,6 @@ class GloomApiClient {
|
|
|
154
156
|
return this.transport.getSocketAuthToken();
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
private async handleSocketMessage(raw: string): Promise<void> {
|
|
158
|
-
await this.socket.handleSocketMessage(raw);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
159
|
async ensureVerifiedSession(): Promise<AuthUser | null> {
|
|
162
160
|
return this.auth.ensureVerifiedSession();
|
|
163
161
|
}
|
|
@@ -198,10 +196,54 @@ class GloomApiClient {
|
|
|
198
196
|
return this.auth.updateAccountProfile(update);
|
|
199
197
|
}
|
|
200
198
|
|
|
199
|
+
async getSyncSnapshot(): Promise<CloudSyncSnapshotResponse> {
|
|
200
|
+
return this.request<CloudSyncSnapshotResponse>("/sync/snapshot", { method: "GET" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async putSyncSnapshot(snapshot: SyncSnapshot, options?: { baseRevision?: number | null }): Promise<CloudSyncPushResponse> {
|
|
204
|
+
return this.request<CloudSyncPushResponse>("/sync/snapshot", {
|
|
205
|
+
method: "PUT",
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
snapshot,
|
|
208
|
+
baseRevision: options?.baseRevision ?? null,
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async updateSyncSettings(update: Partial<SyncSettings>): Promise<SyncSettings> {
|
|
214
|
+
const result = await this.request<{ settings: SyncSettings }>("/sync/settings", {
|
|
215
|
+
method: "PATCH",
|
|
216
|
+
body: JSON.stringify(update),
|
|
217
|
+
});
|
|
218
|
+
if (this.currentUser) {
|
|
219
|
+
this.currentUser = {
|
|
220
|
+
...this.currentUser,
|
|
221
|
+
syncEnabled: result.settings.syncEnabled,
|
|
222
|
+
weeklyRoundupEnabled: result.settings.weeklyRoundupEnabled,
|
|
223
|
+
positionAlertsEnabled: result.settings.positionAlertsEnabled,
|
|
224
|
+
lastSyncAt: result.settings.lastSyncAt ?? this.currentUser.lastSyncAt,
|
|
225
|
+
lastRoundupEmailAt: result.settings.lastRoundupEmailAt ?? this.currentUser.lastRoundupEmailAt,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return result.settings;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getRoundupPreview(): Promise<CloudRoundupPreviewResponse> {
|
|
232
|
+
return this.request<CloudRoundupPreviewResponse>("/sync/roundup/preview", { method: "POST", body: JSON.stringify({}) });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async sendRoundupTestEmail(): Promise<CloudRoundupPreviewResponse> {
|
|
236
|
+
return this.request<CloudRoundupPreviewResponse>("/sync/roundup/test-email", { method: "POST", body: JSON.stringify({}) });
|
|
237
|
+
}
|
|
238
|
+
|
|
201
239
|
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
|
202
240
|
return this.auth.changePassword(currentPassword, newPassword);
|
|
203
241
|
}
|
|
204
242
|
|
|
243
|
+
async deleteAccount(): Promise<void> {
|
|
244
|
+
return this.auth.deleteAccount();
|
|
245
|
+
}
|
|
246
|
+
|
|
205
247
|
async getChannels(): Promise<ChatChannel[]> {
|
|
206
248
|
return this.chat.getChannels();
|
|
207
249
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { CloudApiRequestTransport } from "./request";
|
|
3
|
+
|
|
4
|
+
function responseWithBody(body: () => Promise<string>): Response {
|
|
5
|
+
return {
|
|
6
|
+
ok: true,
|
|
7
|
+
status: 200,
|
|
8
|
+
headers: new Headers(),
|
|
9
|
+
text: body,
|
|
10
|
+
} as Response;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("CloudApiRequestTransport market deadlines", () => {
|
|
14
|
+
test("aborts when response headers never arrive", async () => {
|
|
15
|
+
let signal: AbortSignal | null | undefined;
|
|
16
|
+
const transport = new CloudApiRequestTransport({
|
|
17
|
+
marketRequestTimeoutMs: 10,
|
|
18
|
+
fetchTransport: async (_url, init) => {
|
|
19
|
+
signal = init?.signal;
|
|
20
|
+
return new Promise<Response>(() => {});
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await expect(transport.request("/market/quote?symbol=AAPL")).rejects.toThrow(
|
|
25
|
+
"Cloud market request timed out after 10ms",
|
|
26
|
+
);
|
|
27
|
+
expect(signal?.aborted).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("keeps the deadline active while reading the response body", async () => {
|
|
31
|
+
let signal: AbortSignal | null | undefined;
|
|
32
|
+
const transport = new CloudApiRequestTransport({
|
|
33
|
+
marketRequestTimeoutMs: 10,
|
|
34
|
+
fetchTransport: async (_url, init) => {
|
|
35
|
+
signal = init?.signal;
|
|
36
|
+
return responseWithBody(async () => new Promise<string>(() => {}));
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await expect(transport.request("/market/history?symbol=AAPL")).rejects.toThrow(
|
|
41
|
+
"Cloud market request timed out after 10ms",
|
|
42
|
+
);
|
|
43
|
+
expect(signal?.aborted).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("preserves a caller abort instead of replacing it with the market deadline", async () => {
|
|
47
|
+
let fetchCalls = 0;
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
controller.abort(new Error("cancelled by caller"));
|
|
50
|
+
const transport = new CloudApiRequestTransport({
|
|
51
|
+
marketRequestTimeoutMs: 100,
|
|
52
|
+
fetchTransport: async () => {
|
|
53
|
+
fetchCalls += 1;
|
|
54
|
+
return responseWithBody(async () => "{}");
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await expect(transport.request("/market/quote?symbol=AAPL", {
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
})).rejects.toThrow("cancelled by caller");
|
|
61
|
+
expect(fetchCalls).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { httpFetch } from "../utils/http-transport";
|
|
2
|
+
import { withDeadline } from "../utils/async-deadline";
|
|
2
3
|
import { ApiRequestError, parseApiErrorMessage } from "./errors";
|
|
3
4
|
|
|
4
5
|
const DEFAULT_API_URL = "https://api.gloom.sh";
|
|
6
|
+
const DEFAULT_MARKET_REQUEST_TIMEOUT_MS = 10_000;
|
|
5
7
|
const SESSION_COOKIE_NAMES = ["__Secure-gloomberb.session_token", "gloomberb.session_token"] as const;
|
|
6
8
|
|
|
7
9
|
type CloudApiResponse = Pick<Response, "ok" | "status" | "headers" | "text">;
|
|
@@ -21,13 +23,28 @@ function getCloudApiBaseUrl(): string {
|
|
|
21
23
|
return process.env.GLOOMBERB_API_URL ?? DEFAULT_API_URL;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function throwIfRequestAborted(signal: AbortSignal | null | undefined): void {
|
|
27
|
+
if (!signal?.aborted) return;
|
|
28
|
+
throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
export class CloudApiRequestTransport {
|
|
25
32
|
private sessionToken: string | null = null;
|
|
26
33
|
private sessionCookieName: SessionCookieName | null = null;
|
|
27
34
|
private websocketToken: string | null = null;
|
|
35
|
+
private readonly fetchTransport: CloudApiFetchTransport | null;
|
|
36
|
+
private readonly marketRequestTimeoutMs: number;
|
|
28
37
|
|
|
29
38
|
readonly baseUrl = getCloudApiBaseUrl();
|
|
30
39
|
|
|
40
|
+
constructor(options: {
|
|
41
|
+
fetchTransport?: CloudApiFetchTransport;
|
|
42
|
+
marketRequestTimeoutMs?: number;
|
|
43
|
+
} = {}) {
|
|
44
|
+
this.fetchTransport = options.fetchTransport ?? null;
|
|
45
|
+
this.marketRequestTimeoutMs = options.marketRequestTimeoutMs ?? DEFAULT_MARKET_REQUEST_TIMEOUT_MS;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
getSessionToken(): string | null {
|
|
32
49
|
return this.sessionToken;
|
|
33
50
|
}
|
|
@@ -61,6 +78,37 @@ export class CloudApiRequestTransport {
|
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
async request<T>(path: string, options?: RequestInit): Promise<T> {
|
|
81
|
+
if (!path.startsWith("/market/")) {
|
|
82
|
+
return this.performRequest<T>(path, options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const callerSignal = options?.signal;
|
|
87
|
+
const abortFromCaller = () => controller.abort(callerSignal?.reason);
|
|
88
|
+
if (callerSignal?.aborted) {
|
|
89
|
+
abortFromCaller();
|
|
90
|
+
} else {
|
|
91
|
+
callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const request = this.performRequest<T>(path, {
|
|
96
|
+
...options,
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
});
|
|
99
|
+
return await withDeadline(
|
|
100
|
+
request,
|
|
101
|
+
this.marketRequestTimeoutMs,
|
|
102
|
+
`Cloud market request timed out after ${this.marketRequestTimeoutMs}ms: ${path}`,
|
|
103
|
+
(error) => controller.abort(error),
|
|
104
|
+
);
|
|
105
|
+
} finally {
|
|
106
|
+
callerSignal?.removeEventListener("abort", abortFromCaller);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async performRequest<T>(path: string, options?: RequestInit): Promise<T> {
|
|
111
|
+
throwIfRequestAborted(options?.signal);
|
|
64
112
|
const headers = new Headers(options?.headers);
|
|
65
113
|
if (!headers.has("Content-Type") && options?.method && options.method !== "GET") {
|
|
66
114
|
headers.set("Content-Type", "application/json");
|
|
@@ -68,21 +116,21 @@ export class CloudApiRequestTransport {
|
|
|
68
116
|
this.setSessionCookieHeader(headers);
|
|
69
117
|
headers.set("Origin", this.baseUrl);
|
|
70
118
|
|
|
71
|
-
const res = await cloudApiFetchTransport(`${this.baseUrl}${path}`, {
|
|
119
|
+
const res = await (this.fetchTransport ?? cloudApiFetchTransport)(`${this.baseUrl}${path}`, {
|
|
72
120
|
...options,
|
|
73
121
|
headers,
|
|
74
122
|
credentials: "include",
|
|
75
123
|
});
|
|
76
|
-
|
|
124
|
+
throwIfRequestAborted(options?.signal);
|
|
77
125
|
this.extractSessionCookie(res);
|
|
126
|
+
const text = await res.text();
|
|
127
|
+
throwIfRequestAborted(options?.signal);
|
|
78
128
|
|
|
79
129
|
if (!res.ok) {
|
|
80
|
-
const
|
|
81
|
-
const msg = parseApiErrorMessage(body);
|
|
130
|
+
const msg = parseApiErrorMessage(text);
|
|
82
131
|
throw new ApiRequestError(msg, res.status);
|
|
83
132
|
}
|
|
84
133
|
|
|
85
|
-
const text = await res.text();
|
|
86
134
|
if (!text) return undefined as T;
|
|
87
135
|
const parsed = JSON.parse(text) as T & { token?: string };
|
|
88
136
|
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
package/src/api-client/types.ts
CHANGED
|
@@ -6,10 +6,9 @@ import type {
|
|
|
6
6
|
HolderData,
|
|
7
7
|
HolderRecord,
|
|
8
8
|
OptionsChain,
|
|
9
|
-
PricePoint,
|
|
10
9
|
Quote,
|
|
11
|
-
TickerFinancials,
|
|
12
10
|
} from "../types/financials";
|
|
11
|
+
import type { SyncSettings, SyncSnapshot } from "../sync/types";
|
|
13
12
|
|
|
14
13
|
export interface ChatUserSummary {
|
|
15
14
|
id: string;
|
|
@@ -20,6 +19,7 @@ export interface ChatUserSummary {
|
|
|
20
19
|
title?: string | null;
|
|
21
20
|
profilePublic?: boolean;
|
|
22
21
|
acceptUnknownDms?: boolean;
|
|
22
|
+
portfolioAnalytics?: PublicPortfolioAnalytics | null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface ChatMessage {
|
|
@@ -83,6 +83,12 @@ export interface AuthUser {
|
|
|
83
83
|
xAccount?: string | null;
|
|
84
84
|
sharedPortfolioId?: string | null;
|
|
85
85
|
acceptUnknownDms?: boolean;
|
|
86
|
+
portfolioAnalytics?: PublicPortfolioAnalytics | null;
|
|
87
|
+
syncEnabled?: boolean;
|
|
88
|
+
weeklyRoundupEnabled?: boolean;
|
|
89
|
+
positionAlertsEnabled?: boolean;
|
|
90
|
+
lastSyncAt?: string | null;
|
|
91
|
+
lastRoundupEmailAt?: string | null;
|
|
86
92
|
createdAt: string;
|
|
87
93
|
updatedAt: string;
|
|
88
94
|
}
|
|
@@ -104,9 +110,20 @@ export interface AccountProfile {
|
|
|
104
110
|
xAccount: string | null;
|
|
105
111
|
sharedPortfolioId: string | null;
|
|
106
112
|
acceptUnknownDms: boolean;
|
|
113
|
+
portfolioAnalytics?: PublicPortfolioAnalytics | null;
|
|
114
|
+
syncEnabled: boolean;
|
|
115
|
+
weeklyRoundupEnabled: boolean;
|
|
116
|
+
positionAlertsEnabled: boolean;
|
|
117
|
+
lastSyncAt: string | null;
|
|
118
|
+
lastRoundupEmailAt: string | null;
|
|
107
119
|
updatedAt: string | null;
|
|
108
120
|
}
|
|
109
121
|
|
|
122
|
+
export interface PublicPortfolioAnalytics {
|
|
123
|
+
oneYearReturn?: number | null;
|
|
124
|
+
spyBeta?: number | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
110
127
|
export interface BuildoutAccountResponse {
|
|
111
128
|
user: {
|
|
112
129
|
id: string;
|
|
@@ -151,8 +168,34 @@ export type AccountProfileUpdate = Partial<{
|
|
|
151
168
|
xAccount: string | null;
|
|
152
169
|
sharedPortfolioId: string | null;
|
|
153
170
|
acceptUnknownDms: boolean;
|
|
171
|
+
portfolioAnalytics: PublicPortfolioAnalytics | null;
|
|
172
|
+
syncEnabled: boolean;
|
|
173
|
+
weeklyRoundupEnabled: boolean;
|
|
174
|
+
positionAlertsEnabled: boolean;
|
|
154
175
|
}>;
|
|
155
176
|
|
|
177
|
+
export interface CloudSyncSnapshotResponse {
|
|
178
|
+
snapshot: SyncSnapshot | null;
|
|
179
|
+
revision: number | null;
|
|
180
|
+
updatedAt: string | null;
|
|
181
|
+
settings: SyncSettings;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface CloudSyncPushResponse {
|
|
185
|
+
revision: number;
|
|
186
|
+
updatedAt: string;
|
|
187
|
+
settings: SyncSettings;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface CloudRoundupPreviewResponse {
|
|
191
|
+
subject: string;
|
|
192
|
+
text: string;
|
|
193
|
+
html: string;
|
|
194
|
+
sender: string;
|
|
195
|
+
replyTo: string;
|
|
196
|
+
recipient: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
156
199
|
export interface CloudQuotePayload extends Quote {
|
|
157
200
|
providerId: "gloomberb-cloud";
|
|
158
201
|
dataSource: "live" | "delayed";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { resolveDesktopDeepLinkAction } from "./desktop-deeplink";
|
|
3
|
+
|
|
4
|
+
describe("desktop deeplinks", () => {
|
|
5
|
+
test("routes cloud roundup links to account management", () => {
|
|
6
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://cloud/roundup?week=2026-07-03")).toEqual({
|
|
7
|
+
type: "open-account-management",
|
|
8
|
+
route: { kind: "cloud-roundup", week: "2026-07-03" },
|
|
9
|
+
message: "Opened weekly roundup settings for 2026-07-03.",
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("routes cloud alert links to account management", () => {
|
|
14
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://cloud/alerts")).toEqual({
|
|
15
|
+
type: "open-account-management",
|
|
16
|
+
route: { kind: "cloud-alerts", week: null },
|
|
17
|
+
message: "Opened portfolio alert settings.",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("routes ticker links with arbitrary registered tab ids", () => {
|
|
22
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://ticker/NVDA?tab=analyst-research")).toEqual({
|
|
23
|
+
type: "open-ticker",
|
|
24
|
+
symbol: "NVDA",
|
|
25
|
+
tabId: "analyst-research",
|
|
26
|
+
message: "Opened NVDA analyst-research tab.",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("routes portfolio and watchlist links", () => {
|
|
31
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://portfolio/main")).toEqual({
|
|
32
|
+
type: "open-collection",
|
|
33
|
+
kind: "portfolio",
|
|
34
|
+
collectionId: "main",
|
|
35
|
+
message: "Opened portfolio main.",
|
|
36
|
+
});
|
|
37
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://watchlist/favorites")).toEqual({
|
|
38
|
+
type: "open-collection",
|
|
39
|
+
kind: "watchlist",
|
|
40
|
+
collectionId: "favorites",
|
|
41
|
+
message: "Opened watchlist favorites.",
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("routes alert links with structured values", () => {
|
|
46
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://alert/new?symbol=nvda&side=above&price=$200")).toEqual({
|
|
47
|
+
type: "create-alert",
|
|
48
|
+
values: { symbol: "NVDA", condition: "above", price: "200" },
|
|
49
|
+
message: "Created NVDA above 200 alert.",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("routes chat links", () => {
|
|
54
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://chat/channel/everyone")).toEqual({
|
|
55
|
+
type: "open-chat-channel",
|
|
56
|
+
channelId: "everyone",
|
|
57
|
+
message: "Opened chat everyone.",
|
|
58
|
+
});
|
|
59
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://chat/dm?users=@vince,@alex")).toEqual({
|
|
60
|
+
type: "open-chat-dm",
|
|
61
|
+
participants: "@vince,@alex",
|
|
62
|
+
message: "Opened DM.",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("routes news links", () => {
|
|
67
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://news?ticker=7203.T")).toEqual({
|
|
68
|
+
type: "open-news",
|
|
69
|
+
kind: "ticker",
|
|
70
|
+
symbol: "7203.T",
|
|
71
|
+
message: "Opened 7203.T news.",
|
|
72
|
+
});
|
|
73
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://news/breaking")).toEqual({
|
|
74
|
+
type: "open-news",
|
|
75
|
+
kind: "breaking",
|
|
76
|
+
symbol: null,
|
|
77
|
+
message: "Opened breaking news.",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("rejects command-bar, non-gloomberb, and malformed links", () => {
|
|
82
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://command?query=profile").type).toBe("unsupported");
|
|
83
|
+
expect(resolveDesktopDeepLinkAction("gloomberb://search/NVDA").type).toBe("unsupported");
|
|
84
|
+
expect(resolveDesktopDeepLinkAction("https://gloom.sh/cloud").type).toBe("unsupported");
|
|
85
|
+
expect(resolveDesktopDeepLinkAction("not a url").type).toBe("unsupported");
|
|
86
|
+
});
|
|
87
|
+
});
|