ctrader-x 0.1.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/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +23 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/spotware-oauth-client.d.ts +31 -0
- package/dist/auth/spotware-oauth-client.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-client.js +65 -0
- package/dist/auth/spotware-oauth-client.js.map +1 -0
- package/dist/auth/spotware-oauth-endpoints.d.ts +3 -0
- package/dist/auth/spotware-oauth-endpoints.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-endpoints.js +8 -0
- package/dist/auth/spotware-oauth-endpoints.js.map +1 -0
- package/dist/auth/spotware-oauth-error.d.ts +6 -0
- package/dist/auth/spotware-oauth-error.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-error.js +15 -0
- package/dist/auth/spotware-oauth-error.js.map +1 -0
- package/dist/auth/spotware-oauth-scope.enum.d.ts +5 -0
- package/dist/auth/spotware-oauth-scope.enum.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-scope.enum.js +9 -0
- package/dist/auth/spotware-oauth-scope.enum.js.map +1 -0
- package/dist/auth/spotware-oauth-token.model.d.ts +7 -0
- package/dist/auth/spotware-oauth-token.model.d.ts.map +1 -0
- package/dist/auth/spotware-oauth-token.model.js +3 -0
- package/dist/auth/spotware-oauth-token.model.js.map +1 -0
- package/dist/auth/spotware-socket-auth-error.d.ts +5 -0
- package/dist/auth/spotware-socket-auth-error.d.ts.map +1 -0
- package/dist/auth/spotware-socket-auth-error.js +13 -0
- package/dist/auth/spotware-socket-auth-error.js.map +1 -0
- package/dist/auth/spotware-socket-authenticator.d.ts +21 -0
- package/dist/auth/spotware-socket-authenticator.d.ts.map +1 -0
- package/dist/auth/spotware-socket-authenticator.js +80 -0
- package/dist/auth/spotware-socket-authenticator.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +19 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/spotware-client.d.ts +56 -0
- package/dist/client/spotware-client.d.ts.map +1 -0
- package/dist/client/spotware-client.js +172 -0
- package/dist/client/spotware-client.js.map +1 -0
- package/dist/client/spotware-request-error.d.ts +5 -0
- package/dist/client/spotware-request-error.d.ts.map +1 -0
- package/dist/client/spotware-request-error.js +13 -0
- package/dist/client/spotware-request-error.js.map +1 -0
- package/dist/example/example.d.ts +2 -0
- package/dist/example/example.d.ts.map +1 -0
- package/dist/example/example.js +187 -0
- package/dist/example/example.js.map +1 -0
- package/dist/example/market-data-example.d.ts +2 -0
- package/dist/example/market-data-example.d.ts.map +1 -0
- package/dist/example/market-data-example.js +32 -0
- package/dist/example/market-data-example.js.map +1 -0
- package/dist/example/shared/create-authenticated-client.d.ts +9 -0
- package/dist/example/shared/create-authenticated-client.d.ts.map +1 -0
- package/dist/example/shared/create-authenticated-client.js +154 -0
- package/dist/example/shared/create-authenticated-client.js.map +1 -0
- package/dist/example/trading-example.d.ts +2 -0
- package/dist/example/trading-example.d.ts.map +1 -0
- package/dist/example/trading-example.js +78 -0
- package/dist/example/trading-example.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/market-data/index.d.ts +3 -0
- package/dist/market-data/index.d.ts.map +1 -0
- package/dist/market-data/index.js +19 -0
- package/dist/market-data/index.js.map +1 -0
- package/dist/market-data/spotware-market-data.d.ts +31 -0
- package/dist/market-data/spotware-market-data.d.ts.map +1 -0
- package/dist/market-data/spotware-market-data.js +68 -0
- package/dist/market-data/spotware-market-data.js.map +1 -0
- package/dist/market-data/spotware-symbol-catalog.d.ts +19 -0
- package/dist/market-data/spotware-symbol-catalog.d.ts.map +1 -0
- package/dist/market-data/spotware-symbol-catalog.js +52 -0
- package/dist/market-data/spotware-symbol-catalog.js.map +1 -0
- package/dist/shared/sleep.d.ts +5 -0
- package/dist/shared/sleep.d.ts.map +1 -0
- package/dist/shared/sleep.js +17 -0
- package/dist/shared/sleep.js.map +1 -0
- package/dist/shared/spotware-scale.d.ts +3 -0
- package/dist/shared/spotware-scale.d.ts.map +1 -0
- package/dist/shared/spotware-scale.js +12 -0
- package/dist/shared/spotware-scale.js.map +1 -0
- package/dist/shared/typed-event-emitter.d.ts +13 -0
- package/dist/shared/typed-event-emitter.d.ts.map +1 -0
- package/dist/shared/typed-event-emitter.js +28 -0
- package/dist/shared/typed-event-emitter.js.map +1 -0
- package/dist/trading/index.d.ts +2 -0
- package/dist/trading/index.d.ts.map +1 -0
- package/dist/trading/index.js +18 -0
- package/dist/trading/index.js.map +1 -0
- package/dist/trading/spotware-trading.d.ts +59 -0
- package/dist/trading/spotware-trading.d.ts.map +1 -0
- package/dist/trading/spotware-trading.js +97 -0
- package/dist/trading/spotware-trading.js.map +1 -0
- package/dist/transport/frame-codec.d.ts +6 -0
- package/dist/transport/frame-codec.d.ts.map +1 -0
- package/dist/transport/frame-codec.js +35 -0
- package/dist/transport/frame-codec.js.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +20 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/rate-limiter.d.ts +11 -0
- package/dist/transport/rate-limiter.d.ts.map +1 -0
- package/dist/transport/rate-limiter.js +48 -0
- package/dist/transport/rate-limiter.js.map +1 -0
- package/dist/transport/reconnect-backoff.d.ts +12 -0
- package/dist/transport/reconnect-backoff.d.ts.map +1 -0
- package/dist/transport/reconnect-backoff.js +20 -0
- package/dist/transport/reconnect-backoff.js.map +1 -0
- package/dist/transport/spotware-host.enum.d.ts +6 -0
- package/dist/transport/spotware-host.enum.d.ts.map +1 -0
- package/dist/transport/spotware-host.enum.js +10 -0
- package/dist/transport/spotware-host.enum.js.map +1 -0
- package/dist/transport/spotware-rate-limiter.d.ts +8 -0
- package/dist/transport/spotware-rate-limiter.d.ts.map +1 -0
- package/dist/transport/spotware-rate-limiter.js +29 -0
- package/dist/transport/spotware-rate-limiter.js.map +1 -0
- package/dist/transport/spotware-transport.d.ts +63 -0
- package/dist/transport/spotware-transport.d.ts.map +1 -0
- package/dist/transport/spotware-transport.js +237 -0
- package/dist/transport/spotware-transport.js.map +1 -0
- package/dist/types/generated/OpenApiCommonMessages.d.ts +49 -0
- package/dist/types/generated/OpenApiCommonMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiCommonMessages.js +285 -0
- package/dist/types/generated/OpenApiCommonMessages.js.map +1 -0
- package/dist/types/generated/OpenApiCommonModelMessages.d.ts +40 -0
- package/dist/types/generated/OpenApiCommonModelMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiCommonModelMessages.js +140 -0
- package/dist/types/generated/OpenApiCommonModelMessages.js.map +1 -0
- package/dist/types/generated/OpenApiMessages.d.ts +964 -0
- package/dist/types/generated/OpenApiMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiMessages.js +8754 -0
- package/dist/types/generated/OpenApiMessages.js.map +1 -0
- package/dist/types/generated/OpenApiModelMessages.d.ts +1116 -0
- package/dist/types/generated/OpenApiModelMessages.d.ts.map +1 -0
- package/dist/types/generated/OpenApiModelMessages.js +6917 -0
- package/dist/types/generated/OpenApiModelMessages.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +141 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Caius Citiriga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ctrader-x
|
|
2
|
+
|
|
3
|
+
A TypeScript SDK for cTrader's Open API — connect, authenticate, stream prices, and trade, with automatic reconnection built in.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
There is no official Spotware SDK for Node.js/TypeScript; official support covers mainly C# and Python. The main community alternative, Mida, went closed-source. Developers integrating the Open API directly hit real, documented friction: a raw TCP/Protobuf protocol with manual message framing, a delegated OAuth2 handshake, and enough moving parts that getting from zero to a connected, authenticated, streaming client takes real effort.
|
|
8
|
+
|
|
9
|
+
ctrader-x exists to remove that friction: a clean, typed, batteries-included client, with resilience (reconnection, backoff, outage detection) handled for you instead of left as an exercise.
|
|
10
|
+
|
|
11
|
+
This is a connectivity/SDK library, not a dashboard or a UI. It gives you the building blocks to talk to cTrader's Open API from your own Node.js backend.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Fully typed, generated directly from Spotware's official `.proto` definitions — no hand-maintained protocol types to drift out of date.
|
|
16
|
+
- A TCP/TLS transport that survives real network outages: automatic reconnection with exponential backoff and jitter, a liveness watchdog that notices a silently dead connection before the OS would, and Spotware's documented rate limits enforced automatically.
|
|
17
|
+
- OAuth2 authorization flow plus the socket-level authentication handshake, re-run automatically after every reconnect.
|
|
18
|
+
- Request/response correlation, so `client.send(...)` resolves or rejects with the matching response instead of requiring you to track `clientMsgId`s yourself.
|
|
19
|
+
- Market data subscriptions with automatic resubscription after a reconnect, and a symbol catalog you can use standalone.
|
|
20
|
+
- Order placement, amendment, cancellation, position closing, and querying open positions/pending orders.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install ctrader-x
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Node.js 18 or later.
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
Before writing any code, register an application in the [cTrader Open API portal](https://help.ctrader.com/open-api/) to obtain a `clientId` and `clientSecret`, and register a redirect URI for the OAuth2 flow (e.g. `http://localhost:3939/callback`). You'll also need at least one demo account linked to your cTID to test against — start on `SpotwareHost.DEMO`, not `SpotwareHost.LIVE`, until you're confident in your integration.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
The fastest way to see it working end to end — browser login and all — is the included example:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone git@github.com:caiusCitiriga/ctrader-x.git
|
|
40
|
+
cd ctrader-x
|
|
41
|
+
npm install
|
|
42
|
+
npm start
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Connecting in your own code
|
|
46
|
+
|
|
47
|
+
Once you have an access token and know which account you're connecting to (see [Authenticating](#authenticating-for-the-first-time) below), connecting is this:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { SpotwareClient, SpotwareHost, SpotwareOAuthClient, SpotwareTransport } from 'ctrader-x';
|
|
51
|
+
|
|
52
|
+
const oauthClient = new SpotwareOAuthClient({ clientId, clientSecret });
|
|
53
|
+
|
|
54
|
+
const client = new SpotwareClient({
|
|
55
|
+
transport: new SpotwareTransport({ host: SpotwareHost.DEMO }),
|
|
56
|
+
oauthClient,
|
|
57
|
+
clientId,
|
|
58
|
+
clientSecret,
|
|
59
|
+
ctidTraderAccountId, // the account you're connecting to
|
|
60
|
+
token // { accessToken, refreshToken, tokenType, expiresIn }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await client.connect();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's it — `client` re-authenticates automatically after every reconnect and refreshes the token when it's close to expiring. It's now ready to use with the `market-data` and `trading` modules below.
|
|
67
|
+
|
|
68
|
+
### Authenticating for the first time
|
|
69
|
+
|
|
70
|
+
Getting that `token` and `ctidTraderAccountId` the very first time is a one-time OAuth2 flow, not something you do on every run:
|
|
71
|
+
|
|
72
|
+
1. Build an authorization URL and send the user to it in a browser.
|
|
73
|
+
2. They log into cTrader and approve access; cTrader redirects back to your `redirectUri` with a `code` query param.
|
|
74
|
+
3. Exchange that code for a token.
|
|
75
|
+
4. Ask the server which account(s) that token can access, and pick one.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const oauthClient = new SpotwareOAuthClient({ clientId, clientSecret });
|
|
79
|
+
const authorizeUrl = oauthClient.buildAuthorizationUrl({ redirectUri, scope: SpotwareOAuthScope.TRADING });
|
|
80
|
+
// open authorizeUrl in a browser, then capture the `code` param cTrader redirects back with
|
|
81
|
+
const token = await oauthClient.exchangeAuthorizationCode({ code, redirectUri });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Step 4 (listing accounts) needs a connected `SpotwareTransport` and a `SpotwareSocketAuthenticator` — rather than duplicate that here, see [`src/example/shared/create-authenticated-client.ts`](src/example/shared/create-authenticated-client.ts) for the complete, runnable version: capturing the redirect with a local HTTP server, discovering linked accounts, and persisting the token to disk. Refresh tokens are single-use, so save the replacement `token` every time `client.on('tokenRefreshed', ...)` fires, or the next run will fail.
|
|
85
|
+
|
|
86
|
+
## Architecture
|
|
87
|
+
|
|
88
|
+
Dependencies only flow one direction, top to bottom in this table: `trading` and `market-data` depend on `client`, which depends on `transport` and `auth`, which depends on `types`. It never goes the other way — `types` has no idea `trading` exists.
|
|
89
|
+
|
|
90
|
+
| Module | Depends on | Responsibility |
|
|
91
|
+
| ------------- | ----------------- | ---------------------------------------------------------------------------------------- |
|
|
92
|
+
| `types` | — | Generated TypeScript types for every Open API Protobuf message. |
|
|
93
|
+
| `transport` | `types` | TCP/TLS connection, message framing, reconnection, rate limiting. |
|
|
94
|
+
| `auth` | `transport` | OAuth2 token flow and the socket-level auth handshake. |
|
|
95
|
+
| `client` | `transport`, `auth`| Request/response correlation, re-auth on reconnect, token refresh. |
|
|
96
|
+
| `market-data` | `client` | Symbol lookup and spot price subscriptions. |
|
|
97
|
+
| `trading` | `client` | Order placement, amendment, cancellation, and position/order queries. |
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
### Market data
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { SpotwareMarketData } from 'ctrader-x';
|
|
105
|
+
|
|
106
|
+
const marketData = new SpotwareMarketData(client);
|
|
107
|
+
|
|
108
|
+
marketData.on('price', (update) => {
|
|
109
|
+
console.log(`${update.symbolId}: bid=${update.bid} ask=${update.ask}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const symbol = await marketData.symbols.findByName('EURUSD');
|
|
113
|
+
if (symbol) {
|
|
114
|
+
await marketData.subscribe(symbol.symbolId);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`marketData.symbols` is a `SpotwareSymbolCatalog` — it can also be used on its own (`new SpotwareSymbolCatalog(client)`) if you only need symbol lookups.
|
|
119
|
+
|
|
120
|
+
### Trading
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { SpotwareTrading, ProtoOATradeSide } from 'ctrader-x';
|
|
124
|
+
|
|
125
|
+
const trading = new SpotwareTrading(client);
|
|
126
|
+
|
|
127
|
+
const execution = await trading.placeMarketOrder({
|
|
128
|
+
symbolId: symbol.symbolId,
|
|
129
|
+
tradeSide: ProtoOATradeSide.BUY,
|
|
130
|
+
volume: 1000 // 1000 units, not lots — see "A note on volume" below
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const { positions, orders } = await trading.getOpenPositionsAndOrders();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### A note on volume
|
|
137
|
+
|
|
138
|
+
`volume` here is in **units** of the symbol's base currency (for EURUSD, 1 unit = 1 EUR), not lots. On the wire, cTrader represents volume as "cents of a unit" — `100000` means `1000.00` units — and `trading` converts to and from that form for you, so you always work in whole units through this API, never the wire's scaled integer.
|
|
139
|
+
|
|
140
|
+
Units and lots are not the same thing, and the conversion between them is **not a fixed ratio you can hardcode**. How many units make up "1 lot" is defined per symbol, not by the protocol — retail forex conventionally uses 100,000 units per lot, but that's a market convention, not something cTrader's API guarantees for every symbol (it can differ for indices, commodities, crypto, or simply per broker). The authoritative values — `lotSize`, plus the tradable `minVolume`/`maxVolume`/`stepVolume` range — live on the full symbol spec (`ProtoOASymbol`, fetched via `PROTO_OA_SYMBOL_BY_ID_REQ`), not on the lighter `ProtoOALightSymbol` that `SpotwareSymbolCatalog` currently exposes. If your UI works in lots, fetch that full spec yourself and convert before calling `trading`; `ctrader-x` doesn't wrap that request yet.
|
|
141
|
+
|
|
142
|
+
More complete, runnable examples live in [`src/example/`](src/example/):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run start:market-data # subscribe to a symbol and print live prices
|
|
146
|
+
npm run start:trading # query positions/orders, place and cancel a safe limit order
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
### Running tests
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm test
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Building
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run build
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Regenerating protocol types
|
|
164
|
+
|
|
165
|
+
`src/types/generated` is produced from Spotware's official `.proto` files in [`proto/`](proto) via `ts-proto`, and is committed to the repo. Whenever `proto/` is refreshed from Spotware's upstream repo, regenerate and commit the diff:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npm run generate:types
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
Issues and pull requests are welcome. Please open an issue to discuss significant changes before submitting a PR.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './spotware-oauth-client';
|
|
2
|
+
export * from './spotware-oauth-error';
|
|
3
|
+
export * from './spotware-oauth-scope.enum';
|
|
4
|
+
export * from './spotware-oauth-token.model';
|
|
5
|
+
export * from './spotware-socket-auth-error';
|
|
6
|
+
export * from './spotware-socket-authenticator';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./spotware-oauth-client"), exports);
|
|
18
|
+
__exportStar(require("./spotware-oauth-error"), exports);
|
|
19
|
+
__exportStar(require("./spotware-oauth-scope.enum"), exports);
|
|
20
|
+
__exportStar(require("./spotware-oauth-token.model"), exports);
|
|
21
|
+
__exportStar(require("./spotware-socket-auth-error"), exports);
|
|
22
|
+
__exportStar(require("./spotware-socket-authenticator"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,yDAAuC;AACvC,8DAA4C;AAC5C,+DAA6C;AAC7C,+DAA6C;AAC7C,kEAAgD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SpotwareOAuthScope } from './spotware-oauth-scope.enum';
|
|
2
|
+
import type { ISpotwareOAuthToken } from './spotware-oauth-token.model';
|
|
3
|
+
export interface ISpotwareOAuthClientOptions {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
}
|
|
7
|
+
export interface IBuildAuthorizationUrlParams {
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
scope: SpotwareOAuthScope;
|
|
10
|
+
}
|
|
11
|
+
export interface IExchangeAuthorizationCodeParams {
|
|
12
|
+
code: string;
|
|
13
|
+
redirectUri: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IRefreshAccessTokenParams {
|
|
16
|
+
refreshToken: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The HTTP half of the OAuth2 flow: builds the authorization URL, exchanges the
|
|
20
|
+
* redirect code for tokens, and refreshes the access token. Knows nothing about
|
|
21
|
+
* the TCP socket or the ApplicationAuthReq/AccountAuthReq handshake.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SpotwareOAuthClient {
|
|
24
|
+
private readonly options;
|
|
25
|
+
constructor(options: ISpotwareOAuthClientOptions);
|
|
26
|
+
buildAuthorizationUrl({ redirectUri, scope }: IBuildAuthorizationUrlParams): string;
|
|
27
|
+
exchangeAuthorizationCode({ code, redirectUri }: IExchangeAuthorizationCodeParams): Promise<ISpotwareOAuthToken>;
|
|
28
|
+
refreshAccessToken({ refreshToken }: IRefreshAccessTokenParams): Promise<ISpotwareOAuthToken>;
|
|
29
|
+
private requestToken;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=spotware-oauth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-client.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-oauth-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,MAAM,WAAW,2BAA2B;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,4BAA4B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,gCAAgC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACtC,YAAY,EAAE,MAAM,CAAC;CACxB;AAWD;;;;GAIG;AACH,qBAAa,mBAAmB;IAChB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,2BAA2B;IAEjE,qBAAqB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,4BAA4B,GAAG,MAAM;IASnF,yBAAyB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,gCAAgC,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAQhH,kBAAkB,CAAC,EAAE,YAAY,EAAE,EAAE,yBAAyB,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAS/E,YAAY;CAgC7B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareOAuthClient = void 0;
|
|
4
|
+
const spotware_oauth_endpoints_1 = require("./spotware-oauth-endpoints");
|
|
5
|
+
const spotware_oauth_error_1 = require("./spotware-oauth-error");
|
|
6
|
+
/**
|
|
7
|
+
* The HTTP half of the OAuth2 flow: builds the authorization URL, exchanges the
|
|
8
|
+
* redirect code for tokens, and refreshes the access token. Knows nothing about
|
|
9
|
+
* the TCP socket or the ApplicationAuthReq/AccountAuthReq handshake.
|
|
10
|
+
*/
|
|
11
|
+
class SpotwareOAuthClient {
|
|
12
|
+
options;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
buildAuthorizationUrl({ redirectUri, scope }) {
|
|
17
|
+
const url = new URL(spotware_oauth_endpoints_1.SPOTWARE_OAUTH_AUTHORIZE_URL);
|
|
18
|
+
url.searchParams.set('client_id', this.options.clientId);
|
|
19
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
20
|
+
url.searchParams.set('scope', scope);
|
|
21
|
+
return url.toString();
|
|
22
|
+
}
|
|
23
|
+
exchangeAuthorizationCode({ code, redirectUri }) {
|
|
24
|
+
return this.requestToken({
|
|
25
|
+
grant_type: 'authorization_code',
|
|
26
|
+
code,
|
|
27
|
+
redirect_uri: redirectUri
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
refreshAccessToken({ refreshToken }) {
|
|
31
|
+
return this.requestToken({
|
|
32
|
+
grant_type: 'refresh_token',
|
|
33
|
+
refresh_token: refreshToken
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Spotware's token endpoint takes these as GET query params, client_secret included,
|
|
37
|
+
// matching their own SDKs. Not our choice of contract, just theirs to interop with.
|
|
38
|
+
async requestToken(params) {
|
|
39
|
+
const url = new URL(spotware_oauth_endpoints_1.SPOTWARE_OAUTH_TOKEN_URL);
|
|
40
|
+
for (const [key, value] of Object.entries(params)) {
|
|
41
|
+
url.searchParams.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
url.searchParams.set('client_id', this.options.clientId);
|
|
44
|
+
url.searchParams.set('client_secret', this.options.clientSecret);
|
|
45
|
+
let response;
|
|
46
|
+
try {
|
|
47
|
+
response = await fetch(url);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new spotware_oauth_error_1.SpotwareOAuthError(`Failed to reach the Spotware token endpoint: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
const payload = (await response.json());
|
|
53
|
+
if (!response.ok || !payload.accessToken || !payload.refreshToken) {
|
|
54
|
+
throw new spotware_oauth_error_1.SpotwareOAuthError(payload.description ?? `Spotware token request failed with HTTP ${response.status}`, payload.errorCode, response.status);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
accessToken: payload.accessToken,
|
|
58
|
+
refreshToken: payload.refreshToken,
|
|
59
|
+
tokenType: payload.tokenType ?? 'bearer',
|
|
60
|
+
expiresIn: payload.expiresIn ?? 0
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.SpotwareOAuthClient = SpotwareOAuthClient;
|
|
65
|
+
//# sourceMappingURL=spotware-oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-client.js","sourceRoot":"","sources":["../../src/auth/spotware-oauth-client.ts"],"names":[],"mappings":";;;AAAA,yEAAoG;AACpG,iEAA4D;AAgC5D;;;;GAIG;AACH,MAAa,mBAAmB;IACC;IAA7B,YAA6B,OAAoC;QAApC,YAAO,GAAP,OAAO,CAA6B;IAAG,CAAC;IAErE,qBAAqB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAgC;QACtE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,uDAA4B,CAAC,CAAC;QAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAErC,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAED,yBAAyB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAoC;QAC7E,OAAO,IAAI,CAAC,YAAY,CAAC;YACrB,UAAU,EAAE,oBAAoB;YAChC,IAAI;YACJ,YAAY,EAAE,WAAW;SAC5B,CAAC,CAAC;IACP,CAAC;IAED,kBAAkB,CAAC,EAAE,YAAY,EAA6B;QAC1D,OAAO,IAAI,CAAC,YAAY,CAAC;YACrB,UAAU,EAAE,eAAe;YAC3B,aAAa,EAAE,YAAY;SAC9B,CAAC,CAAC;IACP,CAAC;IAED,qFAAqF;IACrF,oFAAoF;IAC5E,KAAK,CAAC,YAAY,CAAC,MAA8B;QACrD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,mDAAwB,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAEjE,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACD,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,yCAAkB,CAAC,gDAAiD,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7G,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAkC,CAAC;QAEzE,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAChE,MAAM,IAAI,yCAAkB,CACxB,OAAO,CAAC,WAAW,IAAI,2CAA2C,QAAQ,CAAC,MAAM,EAAE,EACnF,OAAO,CAAC,SAAS,EACjB,QAAQ,CAAC,MAAM,CAClB,CAAC;QACN,CAAC;QAED,OAAO;YACH,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,QAAQ;YACxC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,CAAC;SACpC,CAAC;IACN,CAAC;CACJ;AA7DD,kDA6DC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-endpoints.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-oauth-endpoints.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AACpF,eAAO,MAAM,wBAAwB,2CAA2C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SPOTWARE_OAUTH_TOKEN_URL = exports.SPOTWARE_OAUTH_AUTHORIZE_URL = void 0;
|
|
4
|
+
// Confirmed against Spotware's own OpenApiPy SDK source, not the (partly stale) prose docs:
|
|
5
|
+
// https://github.com/spotware/OpenApiPy/blob/main/ctrader_open_api/endpoints.py
|
|
6
|
+
exports.SPOTWARE_OAUTH_AUTHORIZE_URL = 'https://openapi.ctrader.com/apps/auth';
|
|
7
|
+
exports.SPOTWARE_OAUTH_TOKEN_URL = 'https://openapi.ctrader.com/apps/token';
|
|
8
|
+
//# sourceMappingURL=spotware-oauth-endpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-endpoints.js","sourceRoot":"","sources":["../../src/auth/spotware-oauth-endpoints.ts"],"names":[],"mappings":";;;AAAA,4FAA4F;AAC5F,gFAAgF;AACnE,QAAA,4BAA4B,GAAG,uCAAuC,CAAC;AACvE,QAAA,wBAAwB,GAAG,wCAAwC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class SpotwareOAuthError extends Error {
|
|
2
|
+
readonly errorCode?: string | undefined;
|
|
3
|
+
readonly httpStatus?: number | undefined;
|
|
4
|
+
constructor(message: string, errorCode?: string | undefined, httpStatus?: number | undefined);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=spotware-oauth-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-error.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-oauth-error.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAmB,SAAQ,KAAK;aAGrB,SAAS,CAAC,EAAE,MAAM;aAClB,UAAU,CAAC,EAAE,MAAM;gBAFnC,OAAO,EAAE,MAAM,EACC,SAAS,CAAC,EAAE,MAAM,YAAA,EAClB,UAAU,CAAC,EAAE,MAAM,YAAA;CAK1C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareOAuthError = void 0;
|
|
4
|
+
class SpotwareOAuthError extends Error {
|
|
5
|
+
errorCode;
|
|
6
|
+
httpStatus;
|
|
7
|
+
constructor(message, errorCode, httpStatus) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.errorCode = errorCode;
|
|
10
|
+
this.httpStatus = httpStatus;
|
|
11
|
+
this.name = 'SpotwareOAuthError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.SpotwareOAuthError = SpotwareOAuthError;
|
|
15
|
+
//# sourceMappingURL=spotware-oauth-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-error.js","sourceRoot":"","sources":["../../src/auth/spotware-oauth-error.ts"],"names":[],"mappings":";;;AAAA,MAAa,kBAAmB,SAAQ,KAAK;IAGrB;IACA;IAHpB,YACI,OAAe,EACC,SAAkB,EAClB,UAAmB;QAEnC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,cAAS,GAAT,SAAS,CAAS;QAClB,eAAU,GAAV,UAAU,CAAS;QAGnC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACrC,CAAC;CACJ;AATD,gDASC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-scope.enum.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-oauth-scope.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,kBAAkB;IAC1B,QAAQ,aAAa;IACrB,OAAO,YAAY;CACtB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareOAuthScope = void 0;
|
|
4
|
+
var SpotwareOAuthScope;
|
|
5
|
+
(function (SpotwareOAuthScope) {
|
|
6
|
+
SpotwareOAuthScope["ACCOUNTS"] = "accounts";
|
|
7
|
+
SpotwareOAuthScope["TRADING"] = "trading";
|
|
8
|
+
})(SpotwareOAuthScope || (exports.SpotwareOAuthScope = SpotwareOAuthScope = {}));
|
|
9
|
+
//# sourceMappingURL=spotware-oauth-scope.enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-scope.enum.js","sourceRoot":"","sources":["../../src/auth/spotware-oauth-scope.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,kBAGX;AAHD,WAAY,kBAAkB;IAC1B,2CAAqB,CAAA;IACrB,yCAAmB,CAAA;AACvB,CAAC,EAHW,kBAAkB,kCAAlB,kBAAkB,QAG7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-token.model.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-oauth-token.model.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-oauth-token.model.js","sourceRoot":"","sources":["../../src/auth/spotware-oauth-token.model.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-socket-auth-error.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-socket-auth-error.ts"],"names":[],"mappings":"AAAA,qBAAa,uBAAwB,SAAQ,KAAK;aAG1B,SAAS,CAAC,EAAE,MAAM;gBADlC,OAAO,EAAE,MAAM,EACC,SAAS,CAAC,EAAE,MAAM,YAAA;CAKzC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareSocketAuthError = void 0;
|
|
4
|
+
class SpotwareSocketAuthError extends Error {
|
|
5
|
+
errorCode;
|
|
6
|
+
constructor(message, errorCode) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.errorCode = errorCode;
|
|
9
|
+
this.name = 'SpotwareSocketAuthError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.SpotwareSocketAuthError = SpotwareSocketAuthError;
|
|
13
|
+
//# sourceMappingURL=spotware-socket-auth-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-socket-auth-error.js","sourceRoot":"","sources":["../../src/auth/spotware-socket-auth-error.ts"],"names":[],"mappings":";;;AAAA,MAAa,uBAAwB,SAAQ,KAAK;IAG1B;IAFpB,YACI,OAAe,EACC,SAAkB;QAElC,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,cAAS,GAAT,SAAS,CAAS;QAGlC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IAC1C,CAAC;CACJ;AARD,0DAQC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SpotwareTransport } from '../transport';
|
|
2
|
+
import { ProtoOACtidTraderAccount } from '../types';
|
|
3
|
+
export interface ISpotwareSocketAuthenticatorOptions {
|
|
4
|
+
responseTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The socket half of the auth flow: ApplicationAuthReq, then GetAccountListByAccessTokenReq
|
|
8
|
+
* (the only way to discover a ctidTraderAccountId from a bare access token), then
|
|
9
|
+
* AccountAuthReq. Requires an already-connected transport; knows nothing about how the
|
|
10
|
+
* access token itself was obtained.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SpotwareSocketAuthenticator {
|
|
13
|
+
private readonly transport;
|
|
14
|
+
private readonly responseTimeoutMs;
|
|
15
|
+
constructor(transport: SpotwareTransport, options?: ISpotwareSocketAuthenticatorOptions);
|
|
16
|
+
authenticateApplication(clientId: string, clientSecret: string): Promise<void>;
|
|
17
|
+
listAccounts(accessToken: string): Promise<ProtoOACtidTraderAccount[]>;
|
|
18
|
+
authenticateAccount(ctidTraderAccountId: number, accessToken: string): Promise<void>;
|
|
19
|
+
private sendAndAwait;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=spotware-socket-authenticator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-socket-authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/spotware-socket-authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAIH,wBAAwB,EAO3B,MAAM,UAAU,CAAC;AAKlB,MAAM,WAAW,mCAAmC;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,2BAA2B;IAIhC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAH9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAGtB,SAAS,EAAE,iBAAiB,EAC7C,OAAO,GAAE,mCAAwC;IAK/C,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9E,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC;IActE,mBAAmB,CAAC,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1F,OAAO,CAAC,YAAY;CA0CvB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotwareSocketAuthenticator = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const spotware_socket_auth_error_1 = require("./spotware-socket-auth-error");
|
|
6
|
+
const DEFAULT_RESPONSE_TIMEOUT_MS = 10_000;
|
|
7
|
+
/**
|
|
8
|
+
* The socket half of the auth flow: ApplicationAuthReq, then GetAccountListByAccessTokenReq
|
|
9
|
+
* (the only way to discover a ctidTraderAccountId from a bare access token), then
|
|
10
|
+
* AccountAuthReq. Requires an already-connected transport; knows nothing about how the
|
|
11
|
+
* access token itself was obtained.
|
|
12
|
+
*/
|
|
13
|
+
class SpotwareSocketAuthenticator {
|
|
14
|
+
transport;
|
|
15
|
+
responseTimeoutMs;
|
|
16
|
+
constructor(transport, options = {}) {
|
|
17
|
+
this.transport = transport;
|
|
18
|
+
this.responseTimeoutMs = options.responseTimeoutMs ?? DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
19
|
+
}
|
|
20
|
+
async authenticateApplication(clientId, clientSecret) {
|
|
21
|
+
const request = types_1.ProtoOAApplicationAuthReq.fromPartial({ clientId, clientSecret });
|
|
22
|
+
await this.sendAndAwait(types_1.ProtoMessage.fromPartial({
|
|
23
|
+
payloadType: types_1.ProtoOAPayloadType.PROTO_OA_APPLICATION_AUTH_REQ,
|
|
24
|
+
payload: types_1.ProtoOAApplicationAuthReq.encode(request).finish()
|
|
25
|
+
}), types_1.ProtoOAPayloadType.PROTO_OA_APPLICATION_AUTH_RES);
|
|
26
|
+
}
|
|
27
|
+
async listAccounts(accessToken) {
|
|
28
|
+
const request = types_1.ProtoOAGetAccountListByAccessTokenReq.fromPartial({ accessToken });
|
|
29
|
+
const response = await this.sendAndAwait(types_1.ProtoMessage.fromPartial({
|
|
30
|
+
payloadType: types_1.ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_REQ,
|
|
31
|
+
payload: types_1.ProtoOAGetAccountListByAccessTokenReq.encode(request).finish()
|
|
32
|
+
}), types_1.ProtoOAPayloadType.PROTO_OA_GET_ACCOUNTS_BY_ACCESS_TOKEN_RES);
|
|
33
|
+
return types_1.ProtoOAGetAccountListByAccessTokenRes.decode(response.payload ?? new Uint8Array()).ctidTraderAccount;
|
|
34
|
+
}
|
|
35
|
+
async authenticateAccount(ctidTraderAccountId, accessToken) {
|
|
36
|
+
const request = types_1.ProtoOAAccountAuthReq.fromPartial({ ctidTraderAccountId, accessToken });
|
|
37
|
+
await this.sendAndAwait(types_1.ProtoMessage.fromPartial({
|
|
38
|
+
payloadType: types_1.ProtoOAPayloadType.PROTO_OA_ACCOUNT_AUTH_REQ,
|
|
39
|
+
payload: types_1.ProtoOAAccountAuthReq.encode(request).finish()
|
|
40
|
+
}), types_1.ProtoOAPayloadType.PROTO_OA_ACCOUNT_AUTH_RES);
|
|
41
|
+
}
|
|
42
|
+
sendAndAwait(message, expectedPayloadType) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let timeout;
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
clearTimeout(timeout);
|
|
47
|
+
this.transport.off('message', onMessage);
|
|
48
|
+
};
|
|
49
|
+
const onMessage = (received) => {
|
|
50
|
+
if (received.payloadType === expectedPayloadType) {
|
|
51
|
+
cleanup();
|
|
52
|
+
resolve(received);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (received.payloadType === types_1.ProtoOAPayloadType.PROTO_OA_ERROR_RES) {
|
|
56
|
+
const error = types_1.ProtoOAErrorRes.decode(received.payload ?? new Uint8Array());
|
|
57
|
+
cleanup();
|
|
58
|
+
reject(new spotware_socket_auth_error_1.SpotwareSocketAuthError(error.description ?? error.errorCode, error.errorCode));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (received.payloadType === types_1.ProtoPayloadType.ERROR_RES) {
|
|
62
|
+
const error = types_1.ProtoErrorRes.decode(received.payload ?? new Uint8Array());
|
|
63
|
+
cleanup();
|
|
64
|
+
reject(new spotware_socket_auth_error_1.SpotwareSocketAuthError(error.description ?? error.errorCode, error.errorCode));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
timeout = setTimeout(() => {
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(new spotware_socket_auth_error_1.SpotwareSocketAuthError(`Timed out waiting for payloadType ${expectedPayloadType}`));
|
|
70
|
+
}, this.responseTimeoutMs);
|
|
71
|
+
this.transport.on('message', onMessage);
|
|
72
|
+
this.transport.send(message).catch((error) => {
|
|
73
|
+
cleanup();
|
|
74
|
+
reject(error);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.SpotwareSocketAuthenticator = SpotwareSocketAuthenticator;
|
|
80
|
+
//# sourceMappingURL=spotware-socket-authenticator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotware-socket-authenticator.js","sourceRoot":"","sources":["../../src/auth/spotware-socket-authenticator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./spotware-client"), exports);
|
|
18
|
+
__exportStar(require("./spotware-request-error"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC"}
|