@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 +9 -1
- package/README.md +37 -2
- package/dist/index.cjs +86 -17
- package/dist/index.d.cts +28 -7
- package/dist/index.d.ts +28 -7
- package/dist/index.js +76 -19
- package/package.json +5 -2
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
|
|
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
|
-
- [
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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: "#
|
|
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: "#
|
|
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(
|
|
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: "#
|
|
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
|
-
|
|
1390
|
-
|
|
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 ===
|
|
1456
|
-
if (event.data.type ===
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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: "#
|
|
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: "#
|
|
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(
|
|
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: "#
|
|
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
|
-
|
|
1367
|
-
|
|
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 ===
|
|
1433
|
-
if (event.data.type ===
|
|
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
|
+
"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.
|
|
46
|
+
"@trade-it/core": "^1.0.0"
|
|
44
47
|
},
|
|
45
48
|
"peerDependencies": {
|
|
46
49
|
"react": "^18.0.0 || ^19.0.0",
|