@trade-it/react 0.3.0 → 1.0.0

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/CHANGELOG.md CHANGED
@@ -4,10 +4,18 @@ All notable changes to `@trade-it/react` will be documented in this file.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.0.0 - 2026-10-15
8
+
9
+ ### Added
10
+
11
+ - Portal onboarding with email OTP or social sign-in, a typed async `onAuthorization` callback, and exchange acknowledgments
12
+ - Exact iframe-source checks and automatic closure deferred until authorization is saved
13
+
7
14
  ### Changed
8
15
 
9
- - Size the desktop Connect modal around the embedded connect flow while preserving the wider Trade modal
16
+ - Size the desktop Connection portal around the embedded connect flow while preserving the wider Trade modal
10
17
  - Keep Connect full-screen on mobile and tall enough for the connected-account continuation state and embedded footer
18
+ - Use a dark loading screen and animate the Trade It logo from its recognizable shape
11
19
 
12
20
  ## 0.2.0 - 2026-07-27
13
21
 
package/README.md CHANGED
@@ -21,10 +21,45 @@ Full integration guides, examples, and API references:
21
21
 
22
22
  - [React SDK Overview](https://docs.tradeit.app/sdk)
23
23
  - [Authentication](https://docs.tradeit.app/sdk/authentication)
24
- - [Connect a Brokerage](https://docs.tradeit.app/sdk/connect)
24
+ - [Portal · Connection mode](https://docs.tradeit.app/sdk/connect)
25
25
  - [Place Trades](https://docs.tradeit.app/sdk/trades)
26
26
  - [SDK Enums Reference](https://docs.tradeit.app/sdk/enums)
27
27
 
28
28
  ## Notes
29
29
 
30
- - Use the Trade It React skill to have your AI agent integrate Trade It: `npx skills add trade-it-inc/react-sdk-skill`
30
+ - Use the Trade It skill to have your AI agent integrate Trade It: `npx skills add trade-it-inc/skills --skill trade-it-skill`
31
+
32
+ ## Trade It Portal
33
+
34
+ Your backend calls `POST /api/portal`: use Basic client credentials with `oauth_options` to select Onboarding mode, or the user’s Bearer access token without `oauth_options` to select Connection mode. Pass the returned URL to `openConnect`. Your backend supplies a registered `redirect_uri` on the embedding website’s origin. Include `oauth_options.email_hint` to start with an email code, or omit it to offer Google, Apple, X, Discord, and email sign-in. Users can switch methods; social sign-in opens a popup and returns to the same portal. Trade It explains access, authenticates the user, and opens brokerage connection inside the same iframe. Your backend can optionally supply `connect_options.brokerage_code` or `connect_options.brokerage_filter` when creating the portal URL, using the same behavior as a connect session.
35
+
36
+ ```tsx
37
+ const tradeIt = useTradeIt({
38
+ onAuthorization: async authorization => {
39
+ const response = await fetch('/api/tradeit/complete', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify(authorization),
43
+ });
44
+ if (!response.ok) throw new Error('Could not save Trade It access');
45
+ },
46
+ });
47
+
48
+ tradeIt.openConnect({ launch: { mode: 'connect', url: onboardingUrl } });
49
+ ```
50
+
51
+ `onAuthorization` receives `{ code, state, issuer }`; resolve it only after your authenticated backend validates the attempt and saves the OAuth tokens. Rejection is reported inside the iframe. Brokerage connection starts immediately, and automatic closure waits for the callback. Credential events do not go to the generic `onEvent` callback.
52
+
53
+ See the [Onboarding mode guide](https://docs.tradeit.app/sdk/onboarding) for the server endpoints, PKCE/state validation, and account ownership rules. The same SDK method opens subsequent connections without another authorization event. Standard redirect OAuth remains available for custom sign-in experiences.
54
+
55
+ ## Releasing
56
+
57
+ Add notes under **Unreleased** in `CHANGELOG.md` and commit your changes first. From the repository root, prepare a release with:
58
+
59
+ ```bash
60
+ npm run release:minor --workspace=@trade-it/react
61
+ ```
62
+
63
+ Use `release:major` or `release:patch` for other increments. The script versions the changelog, bumps the package and lockfile, then creates a commit and a package-specific tag. When releasing core too, prepare core first so React includes its updated dependency.
64
+
65
+ To release both SDK packages together, use `npm run release:sdk:minor` (or `major` / `patch`) from the root. Push the commits and tags, then use the existing SDK publish workflow or `npm run publish:sdk`. Release preparation does not build, publish, or push.
package/dist/index.cjs CHANGED
@@ -58,7 +58,7 @@ function makeProj(yaw, tilt, cx, cy, scale) {
58
58
  function paint(ctx, dots) {
59
59
  for (const dot of dots) {
60
60
  const alpha = dot.a ?? 1;
61
- ctx.fillStyle = `rgba(11,12,14,${alpha})`;
61
+ ctx.fillStyle = `rgba(240,240,242,${alpha})`;
62
62
  ctx.beginPath();
63
63
  ctx.arc(dot.x, dot.y, dot.r, 0, Math.PI * 2);
64
64
  ctx.fill();
@@ -67,7 +67,7 @@ function paint(ctx, dots) {
67
67
  function paintLines(ctx, lines) {
68
68
  for (const line of lines) {
69
69
  const alpha = line.a ?? 1;
70
- ctx.strokeStyle = `rgba(11,12,14,${alpha})`;
70
+ ctx.strokeStyle = `rgba(240,240,242,${alpha})`;
71
71
  ctx.lineWidth = line.w;
72
72
  ctx.beginPath();
73
73
  ctx.moveTo(line.x1, line.y1);
@@ -171,7 +171,7 @@ const frameLogoWait = (size, t, o, logo) => {
171
171
  const R = size / 2 * .82;
172
172
  const rs = radiusScale(size, o.rsPow ?? .6);
173
173
  const b = beatAt(t, o.dwell ?? 5.5, o.morph ?? 1.9, o.turns ?? 0, o.settle ?? .1, o.expo ?? .3);
174
- const m = b.m;
174
+ const m = 1 - b.m;
175
175
  const c = 1 - m;
176
176
  const pt = makeProj((o.yawAmp ?? .22) * Math.sin(t * (o.yawRate ?? .3)) * c, (o.tilt ?? .42) * c, cx, cx, R);
177
177
  const rings = Math.max(3, Math.round(o.rings ?? 9));
@@ -1240,7 +1240,7 @@ function TradeItLogoLoader({ size = 120 }) {
1240
1240
  paintFrame(ctx, frame);
1241
1241
  };
1242
1242
  if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
1243
- render(2.7);
1243
+ render(0);
1244
1244
  return;
1245
1245
  }
1246
1246
  let raf = 0;
@@ -1294,7 +1294,7 @@ function TradeItLogoLoader({ size = 120 }) {
1294
1294
  const LOADING_STYLE = {
1295
1295
  position: "absolute",
1296
1296
  inset: 0,
1297
- background: "#ffffff"
1297
+ background: "#0e1011"
1298
1298
  };
1299
1299
  const LOADING_CONTENT_STYLE = {
1300
1300
  position: "relative",
@@ -1335,10 +1335,11 @@ const DIALOG_DESKTOP_STYLE = {
1335
1335
  width: "min(960px, calc(100vw - 24px))",
1336
1336
  height: "min(760px, calc(100vh - 24px))",
1337
1337
  boxSizing: "border-box",
1338
- background: "#fff",
1338
+ background: "#0e1011",
1339
+ colorScheme: "dark",
1339
1340
  borderRadius: 18,
1340
1341
  boxShadow: "0 24px 80px rgba(2, 6, 23, 0.28)",
1341
- border: "1px solid rgba(148, 163, 184, 0.35)",
1342
+ border: "1px solid rgba(255, 255, 255, 0.14)",
1342
1343
  overflow: "hidden"
1343
1344
  };
1344
1345
  const CONNECT_DIALOG_DESKTOP_STYLE = {
@@ -1377,7 +1378,8 @@ const IFRAME_STYLE = {
1377
1378
  width: "100%",
1378
1379
  height: "100%",
1379
1380
  border: 0,
1380
- background: "#fff"
1381
+ background: "#0e1011",
1382
+ colorScheme: "dark"
1381
1383
  };
1382
1384
  function useIsMobile(maxWidth = 640) {
1383
1385
  const [isMobile, setIsMobile] = (0, react.useState)(false);
@@ -1386,20 +1388,23 @@ function useIsMobile(maxWidth = 640) {
1386
1388
  const mediaQuery = window.matchMedia(`(max-width: ${maxWidth}px)`);
1387
1389
  const onChange = () => setIsMobile(mediaQuery.matches);
1388
1390
  onChange();
1389
- if (typeof mediaQuery.addEventListener === "function") {
1390
- mediaQuery.addEventListener("change", onChange);
1391
- return () => mediaQuery.removeEventListener("change", onChange);
1392
- }
1393
- mediaQuery.addListener(onChange);
1394
- return () => mediaQuery.removeListener(onChange);
1391
+ mediaQuery.addEventListener("change", onChange);
1392
+ return () => mediaQuery.removeEventListener("change", onChange);
1395
1393
  }, [maxWidth]);
1396
1394
  return isMobile;
1397
1395
  }
1398
1396
  function TradeItModal(props) {
1399
- const { open, onOpenChange, onEvent, onError, iframeTitle, keepMounted = false, closeOnEscape = true, closeOnOverlayClick = true, closeOnCloseRequestedEvent = true, showCloseButton = true, zIndex = 9999, title, className } = props;
1397
+ const { open, onOpenChange, onEvent, onAuthorization, onError, iframeTitle, keepMounted = false, closeOnEscape = true, closeOnOverlayClick = true, closeOnCloseRequestedEvent = true, showCloseButton = true, zIndex = 9999, title, className } = props;
1400
1398
  const isMobile = useIsMobile();
1401
1399
  const [isMounted, setIsMounted] = (0, react.useState)(false);
1402
1400
  const [iframeLoaded, setIframeLoaded] = (0, react.useState)(false);
1401
+ const iframeRef = (0, react.useRef)(null);
1402
+ const authorizationRef = (0, react.useRef)({
1403
+ code: "",
1404
+ pending: false,
1405
+ failed: false,
1406
+ closeRequested: false
1407
+ });
1403
1408
  const resolved = (0, react.useMemo)(() => {
1404
1409
  try {
1405
1410
  return {
@@ -1423,6 +1428,12 @@ function TradeItModal(props) {
1423
1428
  (0, react.useEffect)(() => {
1424
1429
  if (!open) return;
1425
1430
  setIframeLoaded(false);
1431
+ authorizationRef.current = {
1432
+ code: "",
1433
+ pending: false,
1434
+ failed: false,
1435
+ closeRequested: false
1436
+ };
1426
1437
  }, [open, resolved.url]);
1427
1438
  (0, react.useEffect)(() => {
1428
1439
  if (!open || !closeOnEscape) return;
@@ -1450,10 +1461,54 @@ function TradeItModal(props) {
1450
1461
  if (!expectedOrigin) return;
1451
1462
  const handleMessage = (event) => {
1452
1463
  if (event.origin !== expectedOrigin) return;
1464
+ if (event.source !== iframeRef.current?.contentWindow) return;
1453
1465
  if (!(0, __trade_it_core.isTradeItEvent)(event.data)) return;
1466
+ if (event.data.type === __trade_it_core.EmbedEventType.Authorization) {
1467
+ if (!(0, __trade_it_core.isTradeItAuthorization)(event.data.payload)) return;
1468
+ const authorization = event.data.payload;
1469
+ if (authorizationRef.current.code === authorization.code) return;
1470
+ const delivery = {
1471
+ code: authorization.code,
1472
+ pending: true,
1473
+ failed: false,
1474
+ closeRequested: false
1475
+ };
1476
+ authorizationRef.current = delivery;
1477
+ const frameWindow = iframeRef.current?.contentWindow;
1478
+ const reply = (success) => {
1479
+ if (authorizationRef.current !== delivery || iframeRef.current?.contentWindow !== frameWindow) return;
1480
+ delivery.pending = false;
1481
+ delivery.failed = !success;
1482
+ frameWindow?.postMessage({
1483
+ source: "tradeit",
1484
+ version: 1,
1485
+ type: __trade_it_core.EmbedEventType.AuthorizationResult,
1486
+ payload: {
1487
+ state: authorization.state,
1488
+ success
1489
+ }
1490
+ }, expectedOrigin);
1491
+ if (success && delivery.closeRequested) onOpenChange?.(false);
1492
+ };
1493
+ Promise.resolve().then(() => {
1494
+ if (!onAuthorization) throw new Error("Configure onAuthorization to save the user's OAuth grant.");
1495
+ return onAuthorization(authorization);
1496
+ }).then(() => reply(true), (error) => {
1497
+ reply(false);
1498
+ onError?.(error instanceof Error ? error : new Error("Could not save the OAuth grant."));
1499
+ });
1500
+ return;
1501
+ }
1454
1502
  onEvent?.(event.data);
1455
- if (event.data.type === "tradeit.error") onError?.(new Error((0, __trade_it_core.getTradeItErrorMessage)(event.data)));
1456
- if (event.data.type === "tradeit.close_requested" && closeOnCloseRequestedEvent) onOpenChange?.(false);
1503
+ if (event.data.type === __trade_it_core.EmbedEventType.Error) onError?.(new Error((0, __trade_it_core.getTradeItErrorMessage)(event.data)));
1504
+ if (event.data.type === __trade_it_core.EmbedEventType.CloseRequested && closeOnCloseRequestedEvent) {
1505
+ if (authorizationRef.current.failed) return;
1506
+ if (authorizationRef.current.pending) {
1507
+ authorizationRef.current.closeRequested = true;
1508
+ return;
1509
+ }
1510
+ onOpenChange?.(false);
1511
+ }
1457
1512
  };
1458
1513
  window.addEventListener("message", handleMessage);
1459
1514
  return () => window.removeEventListener("message", handleMessage);
@@ -1461,6 +1516,7 @@ function TradeItModal(props) {
1461
1516
  open,
1462
1517
  resolved.url,
1463
1518
  onEvent,
1519
+ onAuthorization,
1464
1520
  onError,
1465
1521
  closeOnCloseRequestedEvent,
1466
1522
  onOpenChange
@@ -1508,6 +1564,7 @@ function TradeItModal(props) {
1508
1564
  }),
1509
1565
  !iframeLoaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TradeItModalLoader, {}),
1510
1566
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
1567
+ ref: iframeRef,
1511
1568
  title: dialogLabel,
1512
1569
  src: resolved.url,
1513
1570
  style: IFRAME_STYLE,
@@ -1636,6 +1693,12 @@ let TradeType = /* @__PURE__ */ function(TradeType$1) {
1636
1693
  }({});
1637
1694
 
1638
1695
  //#endregion
1696
+ Object.defineProperty(exports, 'EmbedEventType', {
1697
+ enumerable: true,
1698
+ get: function () {
1699
+ return __trade_it_core.EmbedEventType;
1700
+ }
1701
+ });
1639
1702
  exports.LegType = LegType;
1640
1703
  exports.OrderDirection = OrderDirection;
1641
1704
  exports.OrderType = OrderType;
@@ -1664,6 +1727,12 @@ Object.defineProperty(exports, 'getUrlOrigin', {
1664
1727
  return __trade_it_core.getUrlOrigin;
1665
1728
  }
1666
1729
  });
1730
+ Object.defineProperty(exports, 'isTradeItAuthorization', {
1731
+ enumerable: true,
1732
+ get: function () {
1733
+ return __trade_it_core.isTradeItAuthorization;
1734
+ }
1735
+ });
1667
1736
  Object.defineProperty(exports, 'isTradeItEvent', {
1668
1737
  enumerable: true,
1669
1738
  get: function () {
package/dist/index.d.cts CHANGED
@@ -52,8 +52,28 @@ declare enum TradeType {
52
52
  * what happened to a contract, so `action` is not meaningful on them.
53
53
  */
54
54
  //#endregion
55
+ //#region ../../lib/types/src/enums/embedding.enums.d.ts
56
+ /** Version-one messages exchanged between a Trade It frame and its host. */
57
+ declare enum EmbedEventType {
58
+ Ready = "tradeit.ready",
59
+ CloseRequested = "tradeit.close_requested",
60
+ BrokerageConnected = "tradeit.brokerage_connected",
61
+ TradeSubmitted = "tradeit.trade_submitted",
62
+ TradeExecuted = "tradeit.trade_executed",
63
+ Error = "tradeit.error",
64
+ LoginRequired = "tradeit.login_required",
65
+ Authorization = "tradeit.authorization",
66
+ AuthorizationResult = "tradeit.authorization_result",
67
+ }
68
+ //#endregion
55
69
  //#region ../trade-it-core/src/types.d.ts
56
- type TradeItEventType = 'tradeit.ready' | 'tradeit.close_requested' | 'tradeit.brokerage_connected' | 'tradeit.trade_submitted' | 'tradeit.trade_executed' | 'tradeit.error' | 'tradeit.login_required';
70
+ type TradeItEventType = `${EmbedEventType}`;
71
+ /** OAuth artifacts forwarded to the partner's authenticated backend. */
72
+ interface TradeItAuthorization {
73
+ code: string;
74
+ state: string;
75
+ issuer: string;
76
+ }
57
77
  interface TradeItEvent<TPayload = Record<string, unknown>> {
58
78
  source: 'tradeit';
59
79
  version: 1;
@@ -98,11 +118,7 @@ interface TradeItExitTrigger {
98
118
  value: number;
99
119
  unit: '$' | '%';
100
120
  }
101
- /**
102
- * Query params accepted by Trade It's /t/[ticker] page.
103
- * User-facing config uses descriptive keys; core converts them to the short-key
104
- * query format used by the page parser (tt, t, dir, ot, a, u, lp, sp, tif, tp, sl, ref, legs).
105
- */
121
+ /** Descriptive trade configuration serialized into the page's compact query parameters. */
106
122
  interface TradeItTradeConfigBase {
107
123
  ticker: string;
108
124
  orderType?: OrderType;
@@ -142,7 +158,10 @@ type TradeItConnectLaunch = TradeItLaunch<'connect'>;
142
158
  type TradeItTradeLaunch = TradeItLaunch<'trade'>;
143
159
  //#endregion
144
160
  //#region ../trade-it-core/src/events.d.ts
161
+ /** Validates supported embedded messages before dispatch. */
145
162
  declare function isTradeItEvent(data: unknown): data is TradeItEvent;
163
+ /** Narrows credential events before they can reach an integration callback. */
164
+ declare function isTradeItAuthorization(data: unknown): data is TradeItAuthorization;
146
165
  declare function getTradeItErrorMessage(event: TradeItEvent): string;
147
166
  //#endregion
148
167
  //#region ../trade-it-core/src/url.d.ts
@@ -159,6 +178,8 @@ interface TradeItModalAppearance {
159
178
  title?: string;
160
179
  }
161
180
  interface TradeItModalCallbacks {
181
+ /** Resolve only once your backend has validated the attempt and saved the OAuth tokens. */
182
+ onAuthorization?: (authorization: TradeItAuthorization) => Promise<void>;
162
183
  onOpenChange?: (open: boolean) => void;
163
184
  onEvent?: (event: TradeItEvent) => void;
164
185
  onError?: (error: Error) => void;
@@ -197,4 +218,4 @@ interface UseTradeItReturn {
197
218
  }
198
219
  declare function useTradeIt(defaults?: UseTradeItOptions): UseTradeItReturn;
199
220
  //#endregion
200
- export { LegType, type OpenConnectOptions, type OpenTradeOptions, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, type TradeItConnectLaunch, type TradeItEvent, type TradeItEventType, type TradeItExitTrigger, type TradeItLaunch, TradeItModal, type TradeItModalAppearance, type TradeItModalProps, type TradeItMultiLegTradeConfig, type TradeItMultiLegTradeLegConfig, type TradeItSimpleTradeConfig, type TradeItTradeConfig, type TradeItTradeLaunch, type TradeLinkLegConfig, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, type UseTradeItOptions, getTradeItErrorMessage, getUrlOrigin, isTradeItEvent, resolveLaunchUrl, useTradeIt };
221
+ export { EmbedEventType, LegType, type OpenConnectOptions, type OpenTradeOptions, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, type TradeItAuthorization, type TradeItConnectLaunch, type TradeItEvent, type TradeItEventType, type TradeItExitTrigger, type TradeItLaunch, TradeItModal, type TradeItModalAppearance, type TradeItModalProps, type TradeItMultiLegTradeConfig, type TradeItMultiLegTradeLegConfig, type TradeItSimpleTradeConfig, type TradeItTradeConfig, type TradeItTradeLaunch, type TradeLinkLegConfig, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, type UseTradeItOptions, getTradeItErrorMessage, getUrlOrigin, isTradeItAuthorization, isTradeItEvent, resolveLaunchUrl, useTradeIt };
package/dist/index.d.ts CHANGED
@@ -52,8 +52,28 @@ declare enum TradeType {
52
52
  * what happened to a contract, so `action` is not meaningful on them.
53
53
  */
54
54
  //#endregion
55
+ //#region ../../lib/types/src/enums/embedding.enums.d.ts
56
+ /** Version-one messages exchanged between a Trade It frame and its host. */
57
+ declare enum EmbedEventType {
58
+ Ready = "tradeit.ready",
59
+ CloseRequested = "tradeit.close_requested",
60
+ BrokerageConnected = "tradeit.brokerage_connected",
61
+ TradeSubmitted = "tradeit.trade_submitted",
62
+ TradeExecuted = "tradeit.trade_executed",
63
+ Error = "tradeit.error",
64
+ LoginRequired = "tradeit.login_required",
65
+ Authorization = "tradeit.authorization",
66
+ AuthorizationResult = "tradeit.authorization_result",
67
+ }
68
+ //#endregion
55
69
  //#region ../trade-it-core/src/types.d.ts
56
- type TradeItEventType = 'tradeit.ready' | 'tradeit.close_requested' | 'tradeit.brokerage_connected' | 'tradeit.trade_submitted' | 'tradeit.trade_executed' | 'tradeit.error' | 'tradeit.login_required';
70
+ type TradeItEventType = `${EmbedEventType}`;
71
+ /** OAuth artifacts forwarded to the partner's authenticated backend. */
72
+ interface TradeItAuthorization {
73
+ code: string;
74
+ state: string;
75
+ issuer: string;
76
+ }
57
77
  interface TradeItEvent<TPayload = Record<string, unknown>> {
58
78
  source: 'tradeit';
59
79
  version: 1;
@@ -98,11 +118,7 @@ interface TradeItExitTrigger {
98
118
  value: number;
99
119
  unit: '$' | '%';
100
120
  }
101
- /**
102
- * Query params accepted by Trade It's /t/[ticker] page.
103
- * User-facing config uses descriptive keys; core converts them to the short-key
104
- * query format used by the page parser (tt, t, dir, ot, a, u, lp, sp, tif, tp, sl, ref, legs).
105
- */
121
+ /** Descriptive trade configuration serialized into the page's compact query parameters. */
106
122
  interface TradeItTradeConfigBase {
107
123
  ticker: string;
108
124
  orderType?: OrderType;
@@ -142,7 +158,10 @@ type TradeItConnectLaunch = TradeItLaunch<'connect'>;
142
158
  type TradeItTradeLaunch = TradeItLaunch<'trade'>;
143
159
  //#endregion
144
160
  //#region ../trade-it-core/src/events.d.ts
161
+ /** Validates supported embedded messages before dispatch. */
145
162
  declare function isTradeItEvent(data: unknown): data is TradeItEvent;
163
+ /** Narrows credential events before they can reach an integration callback. */
164
+ declare function isTradeItAuthorization(data: unknown): data is TradeItAuthorization;
146
165
  declare function getTradeItErrorMessage(event: TradeItEvent): string;
147
166
  //#endregion
148
167
  //#region ../trade-it-core/src/url.d.ts
@@ -159,6 +178,8 @@ interface TradeItModalAppearance {
159
178
  title?: string;
160
179
  }
161
180
  interface TradeItModalCallbacks {
181
+ /** Resolve only once your backend has validated the attempt and saved the OAuth tokens. */
182
+ onAuthorization?: (authorization: TradeItAuthorization) => Promise<void>;
162
183
  onOpenChange?: (open: boolean) => void;
163
184
  onEvent?: (event: TradeItEvent) => void;
164
185
  onError?: (error: Error) => void;
@@ -197,4 +218,4 @@ interface UseTradeItReturn {
197
218
  }
198
219
  declare function useTradeIt(defaults?: UseTradeItOptions): UseTradeItReturn;
199
220
  //#endregion
200
- export { LegType, type OpenConnectOptions, type OpenTradeOptions, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, type TradeItConnectLaunch, type TradeItEvent, type TradeItEventType, type TradeItExitTrigger, type TradeItLaunch, TradeItModal, type TradeItModalAppearance, type TradeItModalProps, type TradeItMultiLegTradeConfig, type TradeItMultiLegTradeLegConfig, type TradeItSimpleTradeConfig, type TradeItTradeConfig, type TradeItTradeLaunch, type TradeLinkLegConfig, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, type UseTradeItOptions, getTradeItErrorMessage, getUrlOrigin, isTradeItEvent, resolveLaunchUrl, useTradeIt };
221
+ export { EmbedEventType, LegType, type OpenConnectOptions, type OpenTradeOptions, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, type TradeItAuthorization, type TradeItConnectLaunch, type TradeItEvent, type TradeItEventType, type TradeItExitTrigger, type TradeItLaunch, TradeItModal, type TradeItModalAppearance, type TradeItModalProps, type TradeItMultiLegTradeConfig, type TradeItMultiLegTradeLegConfig, type TradeItSimpleTradeConfig, type TradeItTradeConfig, type TradeItTradeLaunch, type TradeLinkLegConfig, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, type UseTradeItOptions, getTradeItErrorMessage, getUrlOrigin, isTradeItAuthorization, isTradeItEvent, resolveLaunchUrl, useTradeIt };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TradeLinkParam, getTradeItErrorMessage, getTradeItErrorMessage as getTradeItErrorMessage$1, getUrlOrigin, getUrlOrigin as getUrlOrigin$1, isTradeItEvent, isTradeItEvent as isTradeItEvent$1, resolveLaunchUrl, resolveLaunchUrl as resolveLaunchUrl$1 } from "@trade-it/core";
1
+ import { EmbedEventType, EmbedEventType as EmbedEventType$1, TradeLinkParam, getTradeItErrorMessage, getTradeItErrorMessage as getTradeItErrorMessage$1, getUrlOrigin, getUrlOrigin as getUrlOrigin$1, isTradeItAuthorization, isTradeItAuthorization as isTradeItAuthorization$1, isTradeItEvent, isTradeItEvent as isTradeItEvent$1, resolveLaunchUrl, resolveLaunchUrl as resolveLaunchUrl$1 } from "@trade-it/core";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -35,7 +35,7 @@ function makeProj(yaw, tilt, cx, cy, scale) {
35
35
  function paint(ctx, dots) {
36
36
  for (const dot of dots) {
37
37
  const alpha = dot.a ?? 1;
38
- ctx.fillStyle = `rgba(11,12,14,${alpha})`;
38
+ ctx.fillStyle = `rgba(240,240,242,${alpha})`;
39
39
  ctx.beginPath();
40
40
  ctx.arc(dot.x, dot.y, dot.r, 0, Math.PI * 2);
41
41
  ctx.fill();
@@ -44,7 +44,7 @@ function paint(ctx, dots) {
44
44
  function paintLines(ctx, lines) {
45
45
  for (const line of lines) {
46
46
  const alpha = line.a ?? 1;
47
- ctx.strokeStyle = `rgba(11,12,14,${alpha})`;
47
+ ctx.strokeStyle = `rgba(240,240,242,${alpha})`;
48
48
  ctx.lineWidth = line.w;
49
49
  ctx.beginPath();
50
50
  ctx.moveTo(line.x1, line.y1);
@@ -148,7 +148,7 @@ const frameLogoWait = (size, t, o, logo) => {
148
148
  const R = size / 2 * .82;
149
149
  const rs = radiusScale(size, o.rsPow ?? .6);
150
150
  const b = beatAt(t, o.dwell ?? 5.5, o.morph ?? 1.9, o.turns ?? 0, o.settle ?? .1, o.expo ?? .3);
151
- const m = b.m;
151
+ const m = 1 - b.m;
152
152
  const c = 1 - m;
153
153
  const pt = makeProj((o.yawAmp ?? .22) * Math.sin(t * (o.yawRate ?? .3)) * c, (o.tilt ?? .42) * c, cx, cx, R);
154
154
  const rings = Math.max(3, Math.round(o.rings ?? 9));
@@ -1217,7 +1217,7 @@ function TradeItLogoLoader({ size = 120 }) {
1217
1217
  paintFrame(ctx, frame);
1218
1218
  };
1219
1219
  if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
1220
- render(2.7);
1220
+ render(0);
1221
1221
  return;
1222
1222
  }
1223
1223
  let raf = 0;
@@ -1271,7 +1271,7 @@ function TradeItLogoLoader({ size = 120 }) {
1271
1271
  const LOADING_STYLE = {
1272
1272
  position: "absolute",
1273
1273
  inset: 0,
1274
- background: "#ffffff"
1274
+ background: "#0e1011"
1275
1275
  };
1276
1276
  const LOADING_CONTENT_STYLE = {
1277
1277
  position: "relative",
@@ -1312,10 +1312,11 @@ const DIALOG_DESKTOP_STYLE = {
1312
1312
  width: "min(960px, calc(100vw - 24px))",
1313
1313
  height: "min(760px, calc(100vh - 24px))",
1314
1314
  boxSizing: "border-box",
1315
- background: "#fff",
1315
+ background: "#0e1011",
1316
+ colorScheme: "dark",
1316
1317
  borderRadius: 18,
1317
1318
  boxShadow: "0 24px 80px rgba(2, 6, 23, 0.28)",
1318
- border: "1px solid rgba(148, 163, 184, 0.35)",
1319
+ border: "1px solid rgba(255, 255, 255, 0.14)",
1319
1320
  overflow: "hidden"
1320
1321
  };
1321
1322
  const CONNECT_DIALOG_DESKTOP_STYLE = {
@@ -1354,7 +1355,8 @@ const IFRAME_STYLE = {
1354
1355
  width: "100%",
1355
1356
  height: "100%",
1356
1357
  border: 0,
1357
- background: "#fff"
1358
+ background: "#0e1011",
1359
+ colorScheme: "dark"
1358
1360
  };
1359
1361
  function useIsMobile(maxWidth = 640) {
1360
1362
  const [isMobile, setIsMobile] = useState(false);
@@ -1363,20 +1365,23 @@ function useIsMobile(maxWidth = 640) {
1363
1365
  const mediaQuery = window.matchMedia(`(max-width: ${maxWidth}px)`);
1364
1366
  const onChange = () => setIsMobile(mediaQuery.matches);
1365
1367
  onChange();
1366
- if (typeof mediaQuery.addEventListener === "function") {
1367
- mediaQuery.addEventListener("change", onChange);
1368
- return () => mediaQuery.removeEventListener("change", onChange);
1369
- }
1370
- mediaQuery.addListener(onChange);
1371
- return () => mediaQuery.removeListener(onChange);
1368
+ mediaQuery.addEventListener("change", onChange);
1369
+ return () => mediaQuery.removeEventListener("change", onChange);
1372
1370
  }, [maxWidth]);
1373
1371
  return isMobile;
1374
1372
  }
1375
1373
  function TradeItModal(props) {
1376
- const { open, onOpenChange, onEvent, onError, iframeTitle, keepMounted = false, closeOnEscape = true, closeOnOverlayClick = true, closeOnCloseRequestedEvent = true, showCloseButton = true, zIndex = 9999, title, className } = props;
1374
+ const { open, onOpenChange, onEvent, onAuthorization, onError, iframeTitle, keepMounted = false, closeOnEscape = true, closeOnOverlayClick = true, closeOnCloseRequestedEvent = true, showCloseButton = true, zIndex = 9999, title, className } = props;
1377
1375
  const isMobile = useIsMobile();
1378
1376
  const [isMounted, setIsMounted] = useState(false);
1379
1377
  const [iframeLoaded, setIframeLoaded] = useState(false);
1378
+ const iframeRef = useRef(null);
1379
+ const authorizationRef = useRef({
1380
+ code: "",
1381
+ pending: false,
1382
+ failed: false,
1383
+ closeRequested: false
1384
+ });
1380
1385
  const resolved = useMemo(() => {
1381
1386
  try {
1382
1387
  return {
@@ -1400,6 +1405,12 @@ function TradeItModal(props) {
1400
1405
  useEffect(() => {
1401
1406
  if (!open) return;
1402
1407
  setIframeLoaded(false);
1408
+ authorizationRef.current = {
1409
+ code: "",
1410
+ pending: false,
1411
+ failed: false,
1412
+ closeRequested: false
1413
+ };
1403
1414
  }, [open, resolved.url]);
1404
1415
  useEffect(() => {
1405
1416
  if (!open || !closeOnEscape) return;
@@ -1427,10 +1438,54 @@ function TradeItModal(props) {
1427
1438
  if (!expectedOrigin) return;
1428
1439
  const handleMessage = (event) => {
1429
1440
  if (event.origin !== expectedOrigin) return;
1441
+ if (event.source !== iframeRef.current?.contentWindow) return;
1430
1442
  if (!isTradeItEvent$1(event.data)) return;
1443
+ if (event.data.type === EmbedEventType$1.Authorization) {
1444
+ if (!isTradeItAuthorization$1(event.data.payload)) return;
1445
+ const authorization = event.data.payload;
1446
+ if (authorizationRef.current.code === authorization.code) return;
1447
+ const delivery = {
1448
+ code: authorization.code,
1449
+ pending: true,
1450
+ failed: false,
1451
+ closeRequested: false
1452
+ };
1453
+ authorizationRef.current = delivery;
1454
+ const frameWindow = iframeRef.current?.contentWindow;
1455
+ const reply = (success) => {
1456
+ if (authorizationRef.current !== delivery || iframeRef.current?.contentWindow !== frameWindow) return;
1457
+ delivery.pending = false;
1458
+ delivery.failed = !success;
1459
+ frameWindow?.postMessage({
1460
+ source: "tradeit",
1461
+ version: 1,
1462
+ type: EmbedEventType$1.AuthorizationResult,
1463
+ payload: {
1464
+ state: authorization.state,
1465
+ success
1466
+ }
1467
+ }, expectedOrigin);
1468
+ if (success && delivery.closeRequested) onOpenChange?.(false);
1469
+ };
1470
+ Promise.resolve().then(() => {
1471
+ if (!onAuthorization) throw new Error("Configure onAuthorization to save the user's OAuth grant.");
1472
+ return onAuthorization(authorization);
1473
+ }).then(() => reply(true), (error) => {
1474
+ reply(false);
1475
+ onError?.(error instanceof Error ? error : new Error("Could not save the OAuth grant."));
1476
+ });
1477
+ return;
1478
+ }
1431
1479
  onEvent?.(event.data);
1432
- if (event.data.type === "tradeit.error") onError?.(new Error(getTradeItErrorMessage$1(event.data)));
1433
- if (event.data.type === "tradeit.close_requested" && closeOnCloseRequestedEvent) onOpenChange?.(false);
1480
+ if (event.data.type === EmbedEventType$1.Error) onError?.(new Error(getTradeItErrorMessage$1(event.data)));
1481
+ if (event.data.type === EmbedEventType$1.CloseRequested && closeOnCloseRequestedEvent) {
1482
+ if (authorizationRef.current.failed) return;
1483
+ if (authorizationRef.current.pending) {
1484
+ authorizationRef.current.closeRequested = true;
1485
+ return;
1486
+ }
1487
+ onOpenChange?.(false);
1488
+ }
1434
1489
  };
1435
1490
  window.addEventListener("message", handleMessage);
1436
1491
  return () => window.removeEventListener("message", handleMessage);
@@ -1438,6 +1493,7 @@ function TradeItModal(props) {
1438
1493
  open,
1439
1494
  resolved.url,
1440
1495
  onEvent,
1496
+ onAuthorization,
1441
1497
  onError,
1442
1498
  closeOnCloseRequestedEvent,
1443
1499
  onOpenChange
@@ -1485,6 +1541,7 @@ function TradeItModal(props) {
1485
1541
  }),
1486
1542
  !iframeLoaded && /* @__PURE__ */ jsx(TradeItModalLoader, {}),
1487
1543
  /* @__PURE__ */ jsx("iframe", {
1544
+ ref: iframeRef,
1488
1545
  title: dialogLabel,
1489
1546
  src: resolved.url,
1490
1547
  style: IFRAME_STYLE,
@@ -1613,4 +1670,4 @@ let TradeType = /* @__PURE__ */ function(TradeType$1) {
1613
1670
  }({});
1614
1671
 
1615
1672
  //#endregion
1616
- export { LegType, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, TradeItModal, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, getTradeItErrorMessage, getUrlOrigin, isTradeItEvent, resolveLaunchUrl, useTradeIt };
1673
+ export { EmbedEventType, LegType, OrderDirection, OrderType, PositionEffect, TimeInForce, TradeAction, TradeItModal, TradeLinkParam, TradeType, TradeUnit, TriggerUnit, getTradeItErrorMessage, getUrlOrigin, isTradeItAuthorization, isTradeItEvent, resolveLaunchUrl, useTradeIt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trade-it/react",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "React SDK for embedding Trade It connect and trade experiences into a web app.",
5
5
  "author": "Trade It, Inc.",
6
6
  "license": "MIT",
@@ -37,10 +37,13 @@
37
37
  "scripts": {
38
38
  "build": "tsdown src/index.ts --format esm,cjs --dts --out-dir dist && node ../../scripts/fix-trade-it-sdk-dts.mjs dist/index.d.ts dist/index.d.cts",
39
39
  "dev": "tsdown src/index.ts --format esm,cjs --dts --out-dir dist --watch",
40
+ "release:major": "node ../../scripts/release-trade-it-sdk.mjs react major",
41
+ "release:minor": "node ../../scripts/release-trade-it-sdk.mjs react minor",
42
+ "release:patch": "node ../../scripts/release-trade-it-sdk.mjs react patch",
40
43
  "prepublishOnly": "npm run build"
41
44
  },
42
45
  "dependencies": {
43
- "@trade-it/core": "^0.3.0"
46
+ "@trade-it/core": "^1.0.0"
44
47
  },
45
48
  "peerDependencies": {
46
49
  "react": "^18.0.0 || ^19.0.0",