@bizjaya/finclient 0.1.1 → 0.2.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/README.md +51 -22
- package/dist/index.cjs +371 -161
- package/dist/index.d.cts +3134 -521
- package/dist/index.d.ts +3134 -521
- package/dist/index.js +370 -160
- package/dist/webhook.cjs +49 -0
- package/dist/webhook.d.cts +11 -0
- package/dist/webhook.d.ts +11 -0
- package/dist/webhook.js +24 -0
- package/package.json +13 -2
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
FinApiError: () => FinApiError,
|
|
26
26
|
FinClient: () => FinClient,
|
|
27
27
|
FinHttpTransport: () => FinHttpTransport,
|
|
28
|
-
|
|
28
|
+
FxResource: () => FxResource,
|
|
29
29
|
OrdersResource: () => OrdersResource,
|
|
30
30
|
WebhooksResource: () => WebhooksResource,
|
|
31
31
|
readAllChanges: () => readAllChanges,
|
|
@@ -55,9 +55,6 @@ async function* readAllChanges(readPage, cursor, signal) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// src/http-transport.ts
|
|
59
|
-
var import_node_crypto = require("crypto");
|
|
60
|
-
|
|
61
58
|
// src/api-error.ts
|
|
62
59
|
var FinApiError = class extends Error {
|
|
63
60
|
/** The HTTP status code returned by the API, or 0 when no response arrived (timeout or network failure). */
|
|
@@ -81,21 +78,49 @@ var FinApiError = class extends Error {
|
|
|
81
78
|
}
|
|
82
79
|
};
|
|
83
80
|
|
|
81
|
+
// src/sse.ts
|
|
82
|
+
var FinSseParser = class {
|
|
83
|
+
eventName = null;
|
|
84
|
+
data = [];
|
|
85
|
+
/** Adds one line (without its line break). Returns the event the line completed, if any. */
|
|
86
|
+
add(line) {
|
|
87
|
+
if (!line) return this.flush();
|
|
88
|
+
if (line.startsWith(":")) return null;
|
|
89
|
+
const separator = line.indexOf(":");
|
|
90
|
+
const field = separator < 0 ? line : line.slice(0, separator);
|
|
91
|
+
const value = separator < 0 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
92
|
+
if (field === "event") this.eventName = value;
|
|
93
|
+
else if (field === "data") this.data.push(value);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** Ends the stream: an event still being read is returned when it carries data. */
|
|
97
|
+
complete() {
|
|
98
|
+
return this.data.length ? this.flush() : null;
|
|
99
|
+
}
|
|
100
|
+
flush() {
|
|
101
|
+
const item = this.data.length ? { event: this.eventName ?? "message", data: this.data.join("\n") } : null;
|
|
102
|
+
this.eventName = null;
|
|
103
|
+
this.data = [];
|
|
104
|
+
return item;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
84
108
|
// src/http-transport.ts
|
|
85
109
|
var retryableStatuses = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
86
110
|
var writeMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
87
111
|
var FinHttpTransport = class {
|
|
88
112
|
apiKey;
|
|
89
113
|
baseUrl;
|
|
114
|
+
credentials;
|
|
90
115
|
timeoutMs;
|
|
91
116
|
maxRetries;
|
|
92
117
|
fetcher;
|
|
93
118
|
delay;
|
|
94
119
|
random;
|
|
95
120
|
constructor(options) {
|
|
96
|
-
|
|
97
|
-
this.apiKey
|
|
98
|
-
this.baseUrl = (options.baseUrl ?? "https://api.fincopy.
|
|
121
|
+
this.apiKey = options.apiKey?.trim() ?? "";
|
|
122
|
+
if (!this.apiKey && !options.fetch) throw new TypeError("apiKey is required unless a custom fetch authenticates the member session.");
|
|
123
|
+
this.baseUrl = (options.baseUrl ?? "https://api.fincopy.pro").replace(/\/+$/, "");
|
|
99
124
|
const base = new URL(this.baseUrl);
|
|
100
125
|
if (!/^https?:$/.test(base.protocol) || base.search || base.hash) {
|
|
101
126
|
throw new TypeError("baseUrl must be an absolute HTTP or HTTPS URL without a query or fragment.");
|
|
@@ -105,6 +130,7 @@ var FinHttpTransport = class {
|
|
|
105
130
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) throw new RangeError("timeoutMs must be positive.");
|
|
106
131
|
if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) throw new RangeError("maxRetries cannot be negative.");
|
|
107
132
|
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
133
|
+
this.credentials = options.credentials ?? (this.apiKey ? "same-origin" : "include");
|
|
108
134
|
this.delay = options.retryDelay ?? ((milliseconds, signal) => new Promise((resolve, reject) => {
|
|
109
135
|
if (signal?.aborted) {
|
|
110
136
|
reject(signal.reason);
|
|
@@ -147,14 +173,16 @@ var FinHttpTransport = class {
|
|
|
147
173
|
let response;
|
|
148
174
|
let raw;
|
|
149
175
|
try {
|
|
150
|
-
const headers = new Headers({
|
|
176
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
177
|
+
if (this.apiKey) headers.set("X-Api-Key", this.apiKey);
|
|
151
178
|
if (bodyText !== void 0) headers.set("Content-Type", "application/json");
|
|
152
179
|
if (idempotencyKey !== void 0) headers.set("Idempotency-Key", idempotencyKey);
|
|
153
180
|
response = await this.fetcher(url, {
|
|
154
181
|
method: verb,
|
|
155
182
|
headers,
|
|
156
183
|
body: bodyText,
|
|
157
|
-
signal: controller.signal
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
credentials: this.credentials
|
|
158
186
|
});
|
|
159
187
|
raw = await response.text();
|
|
160
188
|
} catch (error) {
|
|
@@ -219,6 +247,79 @@ var FinHttpTransport = class {
|
|
|
219
247
|
chksum: envelope.chksum ?? null
|
|
220
248
|
};
|
|
221
249
|
}
|
|
250
|
+
/** Reads the event stream until the server closes it or the caller aborts. It does not reconnect. */
|
|
251
|
+
async *requestEvents(path, requestOptions = {}) {
|
|
252
|
+
const url = new URL(path.replace(/^\//, ""), `${this.baseUrl}/`);
|
|
253
|
+
const headers = new Headers({ Accept: "text/event-stream" });
|
|
254
|
+
if (this.apiKey) headers.set("X-Api-Key", this.apiKey);
|
|
255
|
+
let response;
|
|
256
|
+
try {
|
|
257
|
+
response = await this.fetcher(url, {
|
|
258
|
+
method: "GET",
|
|
259
|
+
headers,
|
|
260
|
+
signal: requestOptions.signal,
|
|
261
|
+
credentials: this.credentials
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (requestOptions.signal?.aborted) throw error;
|
|
265
|
+
throw new FinApiError(0, "The FinCopy event stream could not be opened.", null, [], { errorCode: "network", cause: error });
|
|
266
|
+
}
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const raw = await response.text();
|
|
269
|
+
let envelope = null;
|
|
270
|
+
try {
|
|
271
|
+
envelope = raw ? JSON.parse(raw) : null;
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
const fallback = `The FinCopy API request failed with HTTP ${response.status}.`;
|
|
275
|
+
throw new FinApiError(
|
|
276
|
+
response.status,
|
|
277
|
+
envelope?.message || fallback,
|
|
278
|
+
envelope?.code ?? null,
|
|
279
|
+
errorsOf(envelope?.errors),
|
|
280
|
+
{
|
|
281
|
+
errorCode: response.status === 429 ? "rate_limited" : null,
|
|
282
|
+
retryAfterMs: retryAfterMsOf(response.headers.get("Retry-After"))
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (!response.body) throw new FinApiError(response.status, "The FinCopy API returned an empty event stream.", null, [], { errorCode: "empty_response" });
|
|
287
|
+
const reader = response.body.getReader();
|
|
288
|
+
const decoder = new TextDecoder();
|
|
289
|
+
const parser = new FinSseParser();
|
|
290
|
+
let buffer = "";
|
|
291
|
+
try {
|
|
292
|
+
while (true) {
|
|
293
|
+
if (requestOptions.signal?.aborted) throw requestOptions.signal.reason;
|
|
294
|
+
let next;
|
|
295
|
+
try {
|
|
296
|
+
next = await reader.read();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (requestOptions.signal?.aborted) throw error;
|
|
299
|
+
throw new FinApiError(0, "The FinCopy event stream was interrupted.", null, [], { errorCode: "network", cause: error });
|
|
300
|
+
}
|
|
301
|
+
buffer += decoder.decode(next.value, { stream: !next.done });
|
|
302
|
+
const lines = buffer.split(/\r?\n/);
|
|
303
|
+
buffer = lines.pop() ?? "";
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
const item = parser.add(line);
|
|
306
|
+
if (item) yield item;
|
|
307
|
+
}
|
|
308
|
+
if (next.done) {
|
|
309
|
+
if (buffer) {
|
|
310
|
+
const item = parser.add(buffer);
|
|
311
|
+
if (item) yield item;
|
|
312
|
+
}
|
|
313
|
+
const final = parser.complete();
|
|
314
|
+
if (final) yield final;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} finally {
|
|
319
|
+
await reader.cancel().catch(() => void 0);
|
|
320
|
+
reader.releaseLock();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
222
323
|
backoff(retry) {
|
|
223
324
|
return Math.min(maxBackoffMs, 250 * 2 ** retry * (0.75 + this.random() * 0.5));
|
|
224
325
|
}
|
|
@@ -246,128 +347,192 @@ function errorsOf(errors) {
|
|
|
246
347
|
}
|
|
247
348
|
function idempotencyKeyOf(supplied) {
|
|
248
349
|
const key = supplied?.trim();
|
|
249
|
-
if (!key) return (
|
|
350
|
+
if (!key) return newIdempotencyKey();
|
|
250
351
|
if (key.length > 120 || /[\u0000-\u001f\u007f]/.test(key)) {
|
|
251
352
|
throw new TypeError("idempotencyKey must be 1-120 characters without control characters.");
|
|
252
353
|
}
|
|
253
354
|
return key;
|
|
254
355
|
}
|
|
356
|
+
function newIdempotencyKey() {
|
|
357
|
+
const webCrypto = globalThis.crypto;
|
|
358
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
359
|
+
const bytes = new Uint8Array(16);
|
|
360
|
+
if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
|
|
361
|
+
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
362
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
363
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
364
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
365
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
366
|
+
}
|
|
255
367
|
|
|
256
368
|
// src/resources.ts
|
|
257
369
|
function pageQuery(query = {}) {
|
|
258
370
|
const params = new URLSearchParams();
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (value2 !== void 0) params.append(key, String(value2));
|
|
371
|
+
for (const key of ["PageNo", "PageSize"]) {
|
|
372
|
+
const value = query[key];
|
|
373
|
+
if (value !== void 0) params.append(key, String(value));
|
|
263
374
|
}
|
|
264
375
|
if (query.Search !== void 0) params.append("search", query.Search);
|
|
265
|
-
for (const key of ["SortBy", "SrchBy"
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
376
|
+
for (const key of ["SortBy", "SrchBy"])
|
|
377
|
+
(query[key] ?? []).forEach((clause, index) => clause.forEach((value, part) => params.append(`${key}[${index}][${part}]`, value)));
|
|
378
|
+
for (const value of query.Include ?? []) params.append("Include", value);
|
|
379
|
+
return params.toString();
|
|
380
|
+
}
|
|
381
|
+
function queryPath(path, page, filters = {}) {
|
|
382
|
+
const params = new URLSearchParams(pageQuery(page));
|
|
383
|
+
for (const [key, value] of Object.entries(filters)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
384
|
+
const suffix = params.toString();
|
|
385
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
270
386
|
}
|
|
271
|
-
function accountPath(
|
|
272
|
-
if (!
|
|
273
|
-
return `v1/accounts/${
|
|
387
|
+
function accountPath(acno) {
|
|
388
|
+
if (!Number.isSafeInteger(acno) || acno <= 0) throw new RangeError("acno must be a positive safe integer.");
|
|
389
|
+
return `v1/fxs/accounts/${acno}`;
|
|
274
390
|
}
|
|
275
|
-
function numericId(value, name) {
|
|
276
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name}
|
|
391
|
+
function numericId(value, name, allowZero = true) {
|
|
392
|
+
if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1)) throw new RangeError(`${name} is out of range.`);
|
|
277
393
|
return String(value);
|
|
278
394
|
}
|
|
279
|
-
function
|
|
395
|
+
function dateValue(value) {
|
|
280
396
|
return value instanceof Date ? value.toISOString() : value;
|
|
281
397
|
}
|
|
398
|
+
function dateFilters(query) {
|
|
399
|
+
return {
|
|
400
|
+
fromDt: query.fromDt === void 0 ? void 0 : dateValue(query.fromDt),
|
|
401
|
+
toDt: query.toDt === void 0 ? void 0 : dateValue(query.toDt)
|
|
402
|
+
};
|
|
403
|
+
}
|
|
282
404
|
var AccountsResource = class {
|
|
283
405
|
constructor(http) {
|
|
284
406
|
this.http = http;
|
|
285
407
|
}
|
|
286
408
|
http;
|
|
287
|
-
/** Lists
|
|
288
|
-
list(options = {}) {
|
|
289
|
-
return this.http.
|
|
409
|
+
/** Lists reachable accounts, optionally filtered by external reference. */
|
|
410
|
+
list(page = {}, externalRef, options = {}) {
|
|
411
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/accounts", page, { externalRef }), options);
|
|
290
412
|
}
|
|
291
|
-
/** Adds a hosted account
|
|
413
|
+
/** Adds a hosted account. */
|
|
292
414
|
make(command, options = {}) {
|
|
293
|
-
return this.http.request("POST", "v1/accounts
|
|
415
|
+
return this.http.request("POST", "v1/fxs/accounts", command, options);
|
|
416
|
+
}
|
|
417
|
+
/** Creates a demo account. */
|
|
418
|
+
makeDemo(command, options = {}) {
|
|
419
|
+
return this.http.request("POST", "v1/fxs/accounts/demo", command, options);
|
|
294
420
|
}
|
|
295
|
-
/** Reads
|
|
296
|
-
|
|
297
|
-
return this.http.request("GET",
|
|
421
|
+
/** Reads demo availability for one platform. */
|
|
422
|
+
demoAvailability(platform, options = {}) {
|
|
423
|
+
return this.http.request("GET", queryPath("v1/fxs/accounts/demo-availability", void 0, { platform }), void 0, options);
|
|
424
|
+
}
|
|
425
|
+
/** Reads one account by ACNO. */
|
|
426
|
+
get(acno, options = {}) {
|
|
427
|
+
return this.http.request("GET", accountPath(acno), void 0, options);
|
|
298
428
|
}
|
|
299
429
|
/** Sets or clears the account's external reference. */
|
|
300
|
-
setExternalRef(
|
|
301
|
-
return this.http.request("PUT", `${accountPath(
|
|
430
|
+
setExternalRef(acno, command, options = {}) {
|
|
431
|
+
return this.http.request("PUT", `${accountPath(acno)}/external-ref`, command, options);
|
|
432
|
+
}
|
|
433
|
+
/** Reads account settings. */
|
|
434
|
+
getSettings(acno, options = {}) {
|
|
435
|
+
return this.http.request("GET", `${accountPath(acno)}/settings`, void 0, options);
|
|
436
|
+
}
|
|
437
|
+
/** Changes account settings. */
|
|
438
|
+
patchSettings(acno, command, options = {}) {
|
|
439
|
+
return this.http.request("PATCH", `${accountPath(acno)}/settings`, command, options);
|
|
440
|
+
}
|
|
441
|
+
/** Lists pairs for one account. */
|
|
442
|
+
pairs(acno, page = {}, search, options = {}) {
|
|
443
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/pairs`, page, { search }), options);
|
|
444
|
+
}
|
|
445
|
+
/** Reads one pair by PRNO. */
|
|
446
|
+
pair(acno, prno, options = {}) {
|
|
447
|
+
return this.http.request("GET", `${accountPath(acno)}/pairs/${numericId(prno, "prno")}`, void 0, options);
|
|
302
448
|
}
|
|
303
|
-
/**
|
|
304
|
-
|
|
305
|
-
return this.http.request("
|
|
449
|
+
/** Changes one pair's settings. */
|
|
450
|
+
patchPair(acno, prno, command, options = {}) {
|
|
451
|
+
return this.http.request("PATCH", `${accountPath(acno)}/pairs/${numericId(prno, "prno")}`, command, options);
|
|
306
452
|
}
|
|
307
|
-
/**
|
|
308
|
-
|
|
309
|
-
return this.http.request("
|
|
453
|
+
/** Reads the broker symbols and enabled list. */
|
|
454
|
+
symbols(acno, options = {}) {
|
|
455
|
+
return this.http.request("GET", `${accountPath(acno)}/symbols`, void 0, options);
|
|
310
456
|
}
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
return this.http.
|
|
457
|
+
/** Adds or removes enabled symbols. */
|
|
458
|
+
editSymbols(acno, command, options = {}) {
|
|
459
|
+
return this.http.request("PUT", `${accountPath(acno)}/symbols`, command, options);
|
|
314
460
|
}
|
|
315
|
-
/** Reads
|
|
316
|
-
|
|
317
|
-
|
|
461
|
+
/** Reads a symbol's broker specification and latest quote. */
|
|
462
|
+
quote(acno, symbol, options = {}) {
|
|
463
|
+
if (!symbol.trim()) throw new TypeError("symbol is required.");
|
|
464
|
+
return this.http.request("GET", `${accountPath(acno)}/quotes/${encodeURIComponent(symbol)}`, void 0, options);
|
|
465
|
+
}
|
|
466
|
+
/** Lists live orders for one account. */
|
|
467
|
+
orders(acno, page = {}, search, options = {}) {
|
|
468
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/orders`, page, { search }), options);
|
|
469
|
+
}
|
|
470
|
+
/** Reads one live or settled order. */
|
|
471
|
+
order(acno, oid, options = {}) {
|
|
472
|
+
return this.http.request("GET", `${accountPath(acno)}/orders/${numericId(oid, "oid")}`, void 0, options);
|
|
318
473
|
}
|
|
319
|
-
/**
|
|
320
|
-
|
|
321
|
-
return this.http.
|
|
474
|
+
/** Lists settled orders. */
|
|
475
|
+
history(acno, page = {}, search, options = {}) {
|
|
476
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/history`, page, { search }), options);
|
|
477
|
+
}
|
|
478
|
+
/** Reads one settled order by OID. */
|
|
479
|
+
historyItem(acno, oid, options = {}) {
|
|
480
|
+
return this.http.request("GET", `${accountPath(acno)}/history/${numericId(oid, "oid")}`, void 0, options);
|
|
322
481
|
}
|
|
323
482
|
/** Lists HYBRID DCA proposals waiting for an answer. */
|
|
324
|
-
dcaQueue(
|
|
325
|
-
return this.http.request("GET", `${accountPath(
|
|
483
|
+
dcaQueue(acno, options = {}) {
|
|
484
|
+
return this.http.request("GET", `${accountPath(acno)}/dca`, void 0, options);
|
|
326
485
|
}
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
return this.http.request("GET", `${accountPath(
|
|
486
|
+
/** Lists daily performance rows. */
|
|
487
|
+
performance(acno, query = {}) {
|
|
488
|
+
return this.http.request("GET", queryPath(`${accountPath(acno)}/performance`, void 0, dateFilters(query)), void 0, query);
|
|
330
489
|
}
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
return this.http.request("
|
|
490
|
+
/** Reads the account's performance window. */
|
|
491
|
+
performanceWindow(acno, options = {}) {
|
|
492
|
+
return this.http.request("GET", `${accountPath(acno)}/performance/window`, void 0, options);
|
|
334
493
|
}
|
|
335
|
-
/** Reads
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
494
|
+
/** Reads performance bars for an optional UTC date range. */
|
|
495
|
+
performanceBars(acno, query = {}) {
|
|
496
|
+
return this.http.request("GET", queryPath(`${accountPath(acno)}/performance/bars`, void 0, dateFilters(query)), void 0, query);
|
|
497
|
+
}
|
|
498
|
+
/** Lists paged account activity. */
|
|
499
|
+
activity(acno, page = {}, search, options = {}) {
|
|
500
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/activity`, page, { search }), options);
|
|
501
|
+
}
|
|
502
|
+
/** Previews applying a strategy (the account owner's or a FinCopy preset): what each symbol would change. Nothing changes. */
|
|
503
|
+
previewStrategy(acno, command, options = {}) {
|
|
504
|
+
return this.http.request("POST", `${accountPath(acno)}/strategy/preview`, command, options);
|
|
505
|
+
}
|
|
506
|
+
/** Applies a strategy to the account and its symbols; a side holding orders keeps what it must keep. */
|
|
507
|
+
applyStrategy(acno, command, options = {}) {
|
|
508
|
+
return this.http.request("POST", `${accountPath(acno)}/strategy/apply`, command, options);
|
|
509
|
+
}
|
|
510
|
+
/** Lists recent terminal operations. */
|
|
511
|
+
operations(acno, page = {}, options = {}) {
|
|
512
|
+
return this.http.requestPage("GET", queryPath(`${accountPath(acno)}/operations`, page), options);
|
|
513
|
+
}
|
|
514
|
+
/** Queues a broker-password change. */
|
|
515
|
+
changePassword(acno, command, options = {}) {
|
|
516
|
+
return this.http.request("POST", `${accountPath(acno)}/password`, command, options);
|
|
517
|
+
}
|
|
518
|
+
/** Queues a terminal start. */
|
|
519
|
+
start(acno, options = {}) {
|
|
520
|
+
return this.operation(acno, "start", options);
|
|
521
|
+
}
|
|
522
|
+
/** Queues a terminal stop. */
|
|
523
|
+
stop(acno, options = {}) {
|
|
524
|
+
return this.operation(acno, "stop", options);
|
|
525
|
+
}
|
|
526
|
+
/** Queues a terminal restart. */
|
|
527
|
+
restart(acno, options = {}) {
|
|
528
|
+
return this.operation(acno, "restart", options);
|
|
529
|
+
}
|
|
530
|
+
/** Queues account removal. */
|
|
531
|
+
remove(acno, options = {}) {
|
|
532
|
+
return this.http.request("DELETE", accountPath(acno), void 0, options);
|
|
533
|
+
}
|
|
534
|
+
operation(acno, action, options) {
|
|
535
|
+
return this.http.request("POST", `${accountPath(acno)}/${action}`, void 0, options);
|
|
371
536
|
}
|
|
372
537
|
};
|
|
373
538
|
var OrdersResource = class {
|
|
@@ -375,45 +540,110 @@ var OrdersResource = class {
|
|
|
375
540
|
this.http = http;
|
|
376
541
|
}
|
|
377
542
|
http;
|
|
378
|
-
/**
|
|
379
|
-
|
|
380
|
-
return this.http.
|
|
543
|
+
/** Preflights a trade request. */
|
|
544
|
+
preInfo(acno, command, options = {}) {
|
|
545
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/preinfo`, command, options);
|
|
381
546
|
}
|
|
382
|
-
/**
|
|
383
|
-
|
|
384
|
-
return this.http.
|
|
547
|
+
/** Sends one trade action. */
|
|
548
|
+
make(acno, command, options = {}) {
|
|
549
|
+
return this.http.request("POST", `${accountPath(acno)}/trade`, command, options);
|
|
385
550
|
}
|
|
386
|
-
/**
|
|
387
|
-
|
|
388
|
-
return this.http.request("
|
|
551
|
+
/** Sends paired BUY and SELL actions. */
|
|
552
|
+
hedge(acno, command, options = {}) {
|
|
553
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/hedge`, command, options);
|
|
389
554
|
}
|
|
390
|
-
/**
|
|
391
|
-
|
|
392
|
-
return this.http.request("
|
|
555
|
+
/** Modifies a trade or pending order. */
|
|
556
|
+
modify(acno, command, options = {}) {
|
|
557
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/modify`, command, options);
|
|
393
558
|
}
|
|
394
|
-
/**
|
|
395
|
-
|
|
396
|
-
return this.http.request("POST", `${accountPath(
|
|
559
|
+
/** Cancels an order that has not reached the terminal. */
|
|
560
|
+
cancelWaiting(acno, command, options = {}) {
|
|
561
|
+
return this.http.request("POST", `${accountPath(acno)}/trade/cancel`, command, options);
|
|
397
562
|
}
|
|
398
|
-
/** Accepts or rejects one
|
|
399
|
-
answerDca(
|
|
400
|
-
return this.http.request("POST", `${accountPath(
|
|
563
|
+
/** Accepts or rejects one DCA proposal. */
|
|
564
|
+
answerDca(acno, oid, command, options = {}) {
|
|
565
|
+
return this.http.request("POST", `${accountPath(acno)}/dca/${numericId(oid, "oid")}`, command, options);
|
|
401
566
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
567
|
+
};
|
|
568
|
+
var FxResource = class {
|
|
569
|
+
constructor(http) {
|
|
570
|
+
this.http = http;
|
|
405
571
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
572
|
+
http;
|
|
573
|
+
/** Lists pairs across reachable accounts. */
|
|
574
|
+
pairs(query = {}, options = {}) {
|
|
575
|
+
return this.http.requestPage("GET", listPath("v1/fxs/pairs", query), options);
|
|
576
|
+
}
|
|
577
|
+
/** Lists open orders across reachable accounts. */
|
|
578
|
+
orders(query = {}, options = {}) {
|
|
579
|
+
return this.http.requestPage("GET", listPath("v1/fxs/orders", query), options);
|
|
580
|
+
}
|
|
581
|
+
/** Lists settled orders across reachable accounts. */
|
|
582
|
+
history(query = {}, options = {}) {
|
|
583
|
+
return this.http.requestPage("GET", listPath("v1/fxs/history", query), options);
|
|
584
|
+
}
|
|
585
|
+
/** Lists activity across reachable accounts. */
|
|
586
|
+
activity(query = {}, options = {}) {
|
|
587
|
+
return this.http.requestPage("GET", listPath("v1/fxs/activity", query), options);
|
|
588
|
+
}
|
|
589
|
+
/** HYBRID DCA proposals waiting for an answer across reachable accounts, newest first. */
|
|
590
|
+
dca(acno, options = {}) {
|
|
591
|
+
return this.http.request("GET", queryPath("v1/fxs/dca", void 0, { acno }), void 0, options);
|
|
592
|
+
}
|
|
593
|
+
/** The newest terminal operations across reachable accounts. */
|
|
594
|
+
operations(page = {}, acno, options = {}) {
|
|
595
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/operations", page, { acno }), options);
|
|
596
|
+
}
|
|
597
|
+
/** The last 30 days by currency book across reachable accounts. */
|
|
598
|
+
performanceWindow(query = {}) {
|
|
599
|
+
return this.http.request("GET", queryPath("v1/fxs/performance/window", void 0, { acno: query.acno, demo: query.demo }), void 0, query);
|
|
600
|
+
}
|
|
601
|
+
/** Daily performance bars across reachable accounts for an optional UTC date range. */
|
|
602
|
+
performanceBars(query = {}) {
|
|
603
|
+
return this.http.request("GET", queryPath(
|
|
604
|
+
"v1/fxs/performance/bars",
|
|
605
|
+
void 0,
|
|
606
|
+
{ acno: query.acno, demo: query.demo, ...dateFilters(query) }
|
|
607
|
+
), void 0, query);
|
|
608
|
+
}
|
|
609
|
+
/** Lists the strategies you can apply: your own and FinCopy's presets; with `acno`, that account owner's. */
|
|
610
|
+
strategies(acno, options = {}) {
|
|
611
|
+
return this.http.request("GET", queryPath("v1/fxs/strategies", void 0, { acno }), void 0, options);
|
|
612
|
+
}
|
|
613
|
+
/** Reads one strategy. */
|
|
614
|
+
strategy(isno, options = {}) {
|
|
615
|
+
return this.http.request("GET", strategyPath(isno), void 0, options);
|
|
616
|
+
}
|
|
617
|
+
/** Creates one of your own strategies. */
|
|
618
|
+
makeStrategy(command, options = {}) {
|
|
619
|
+
return this.http.request("POST", "v1/fxs/strategies", command, options);
|
|
620
|
+
}
|
|
621
|
+
/** Changes one of your own strategies; fields left unset keep their values. */
|
|
622
|
+
patchStrategy(isno, command, options = {}) {
|
|
623
|
+
return this.http.request("PATCH", strategyPath(isno), command, options);
|
|
624
|
+
}
|
|
625
|
+
/** Deletes one of your own strategies. */
|
|
626
|
+
deleteStrategy(isno, options = {}) {
|
|
627
|
+
return this.http.request("DELETE", strategyPath(isno), void 0, options);
|
|
628
|
+
}
|
|
629
|
+
/** Reads the signals shown for enabled symbols. */
|
|
630
|
+
signals(options = {}) {
|
|
631
|
+
return this.http.request("GET", "v1/fxs/signals", void 0, options);
|
|
632
|
+
}
|
|
633
|
+
/** Lists warning and error activity. */
|
|
634
|
+
alerts(page = {}, options = {}) {
|
|
635
|
+
return this.http.requestPage("GET", queryPath("v1/fxs/alerts", page), options);
|
|
409
636
|
}
|
|
410
|
-
/**
|
|
411
|
-
|
|
412
|
-
|
|
637
|
+
/**
|
|
638
|
+
* Opens the live event stream for reachable accounts. It does not reconnect: it ends when the server closes it (about
|
|
639
|
+
* every 30 minutes) or the connection breaks, so open it again in a loop, waiting a few seconds between attempts.
|
|
640
|
+
*/
|
|
641
|
+
stream(options = {}) {
|
|
642
|
+
return this.http.requestEvents("v1/fxs/stream", options);
|
|
413
643
|
}
|
|
414
|
-
/**
|
|
415
|
-
|
|
416
|
-
return this.http.request("
|
|
644
|
+
/** Searches broker servers available when adding an account. */
|
|
645
|
+
brokers(query, platform, demosOnly, options = {}) {
|
|
646
|
+
return this.http.request("GET", queryPath("v1/brokers", void 0, { Q: query, Platform: platform, DemosOnly: demosOnly }), void 0, options);
|
|
417
647
|
}
|
|
418
648
|
};
|
|
419
649
|
var ChangesResource = class {
|
|
@@ -424,18 +654,13 @@ var ChangesResource = class {
|
|
|
424
654
|
/** Reads one changes page using an optional cursor. */
|
|
425
655
|
get(cursor, take = 500, options = {}) {
|
|
426
656
|
if (!Number.isInteger(take) || take < 1 || take > 1e3) throw new RangeError("take must be between 1 and 1000.");
|
|
427
|
-
|
|
428
|
-
if (cursor) params.set("cursor", cursor);
|
|
429
|
-
return this.http.request("GET", `v1/accounts/changes?${params}`, void 0, options);
|
|
657
|
+
return this.http.request("GET", queryPath("v1/changes", void 0, { cursor, take }), void 0, options);
|
|
430
658
|
}
|
|
431
|
-
/**
|
|
432
|
-
* Streams the available changes page by page, following each returned cursor. Save each page's `Cursor` after handling
|
|
433
|
-
* its `Changes`, so a restart resumes from there.
|
|
434
|
-
*/
|
|
659
|
+
/** Reads changes page by page, following each cursor. */
|
|
435
660
|
readPages(cursor, take = 500, signal) {
|
|
436
661
|
return readChangePages((next, nextSignal) => this.page(next, take, nextSignal), cursor, signal);
|
|
437
662
|
}
|
|
438
|
-
/** Streams
|
|
663
|
+
/** Streams all available changes. Use readPages to retain a resume cursor. */
|
|
439
664
|
readAll(cursor, take = 500, signal) {
|
|
440
665
|
return readAllChanges((next, nextSignal) => this.page(next, take, nextSignal), cursor, signal);
|
|
441
666
|
}
|
|
@@ -451,21 +676,27 @@ var WebhooksResource = class {
|
|
|
451
676
|
http;
|
|
452
677
|
/** Reads this key's webhook configuration. */
|
|
453
678
|
get(options = {}) {
|
|
454
|
-
return this.http.request("GET", "v1/
|
|
679
|
+
return this.http.request("GET", "v1/webhook", void 0, options);
|
|
455
680
|
}
|
|
456
|
-
/** Sets the
|
|
681
|
+
/** Sets the webhook destination and returns its signing secret once. */
|
|
457
682
|
set(command, options = {}) {
|
|
458
|
-
return this.http.request("PUT", "v1/
|
|
683
|
+
return this.http.request("PUT", "v1/webhook", command, options);
|
|
459
684
|
}
|
|
460
685
|
/** Removes this key's webhook. */
|
|
461
686
|
delete(options = {}) {
|
|
462
|
-
return this.http.request("DELETE", "v1/
|
|
687
|
+
return this.http.request("DELETE", "v1/webhook", void 0, options);
|
|
463
688
|
}
|
|
464
|
-
/** Sends a signed test
|
|
689
|
+
/** Sends a signed test delivery. */
|
|
465
690
|
ping(options = {}) {
|
|
466
|
-
return this.http.request("POST", "v1/
|
|
691
|
+
return this.http.request("POST", "v1/webhook/ping", void 0, options);
|
|
467
692
|
}
|
|
468
693
|
};
|
|
694
|
+
function strategyPath(isno) {
|
|
695
|
+
return `v1/fxs/strategies/${numericId(isno, "isno")}`;
|
|
696
|
+
}
|
|
697
|
+
function listPath(path, query) {
|
|
698
|
+
return queryPath(path, query.page, { acno: query.acno, prno: query.prno, demo: query.demo, platform: query.platform, search: query.search });
|
|
699
|
+
}
|
|
469
700
|
|
|
470
701
|
// src/client.ts
|
|
471
702
|
var FinClient = class {
|
|
@@ -475,33 +706,12 @@ var FinClient = class {
|
|
|
475
706
|
this.copy = {
|
|
476
707
|
accounts: new AccountsResource(transport),
|
|
477
708
|
orders: new OrdersResource(transport),
|
|
709
|
+
fx: new FxResource(transport),
|
|
478
710
|
changes: new ChangesResource(transport),
|
|
479
711
|
webhooks: new WebhooksResource(transport)
|
|
480
712
|
};
|
|
481
713
|
}
|
|
482
714
|
};
|
|
483
|
-
|
|
484
|
-
// src/webhook.ts
|
|
485
|
-
var import_node_crypto2 = require("crypto");
|
|
486
|
-
var FinWebhook = class {
|
|
487
|
-
static allowedSkewSeconds = 300;
|
|
488
|
-
/** Verify X-FinCopy-Signature over the timestamp and the exact raw request body. */
|
|
489
|
-
static verify(secret, timestamp, body, signature) {
|
|
490
|
-
if (!secret || !signature) return false;
|
|
491
|
-
const timestampText = String(timestamp);
|
|
492
|
-
if (!/^\d+$/.test(timestampText)) return false;
|
|
493
|
-
const seconds = Number(timestampText);
|
|
494
|
-
if (!Number.isSafeInteger(seconds)) return false;
|
|
495
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
496
|
-
if (now - seconds > this.allowedSkewSeconds || seconds - now > this.allowedSkewSeconds) return false;
|
|
497
|
-
const suppliedHex = signature.startsWith("sha256=") ? signature.slice(7) : signature;
|
|
498
|
-
if (!/^[a-fA-F0-9]{64}$/.test(suppliedHex)) return false;
|
|
499
|
-
const supplied = Buffer.from(suppliedHex, "hex");
|
|
500
|
-
const signedBody = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
501
|
-
const expected = (0, import_node_crypto2.createHmac)("sha256", Buffer.from(secret, "utf8")).update(Buffer.from(`${timestampText}.`, "utf8")).update(signedBody).digest();
|
|
502
|
-
return supplied.length === expected.length && (0, import_node_crypto2.timingSafeEqual)(supplied, expected);
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
715
|
// Annotate the CommonJS export names for ESM import in node:
|
|
506
716
|
0 && (module.exports = {
|
|
507
717
|
AccountsResource,
|
|
@@ -509,7 +719,7 @@ var FinWebhook = class {
|
|
|
509
719
|
FinApiError,
|
|
510
720
|
FinClient,
|
|
511
721
|
FinHttpTransport,
|
|
512
|
-
|
|
722
|
+
FxResource,
|
|
513
723
|
OrdersResource,
|
|
514
724
|
WebhooksResource,
|
|
515
725
|
readAllChanges,
|