Skip to content

Instantly share code, notes, and snippets.

@BMorearty
Created October 17, 2023 20:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BMorearty/2d396b02f8df9097100497b82a629f2b to your computer and use it in GitHub Desktop.
Save BMorearty/2d396b02f8df9097100497b82a629f2b to your computer and use it in GitHub Desktop.
My E*Trade API files

Modified files from EtradeNodeClient, E*Trade’s sample client app:

  • OAuth1Client.js is a modified version of oauth/Client.js
  • session.ts is a heavily modified version of utils/session.js

Original files by me:

  • ETradeAPI.ts is a set of partial TypeScript response types I wrote for the E*Trade API
  • ETradeAuth.ts exports an authorizeETrade function that attempts to authorize E*Trade. It launches a browser window to interactively ask the user for a verification code.

Other files:

  • open.js exports a helper function open that opens a file or uri. I don't remember where I got it or why I didn't use a package.
// Partial response types for E*Trade API
// See https://apisb.etrade.com/docs/api/market/api-quote-v1.html
// and https://apisb.etrade.com/docs/api/market/api-market-v1.html
/*
* Quote API
*/
export type Message = {
description: string;
code: number;
type: string;
};
export type Product = {
symbol: string;
securityType: 'BOND' | 'EQ' | 'INDX' | 'MF' | 'MMF' | 'OPTN';
};
export type AllQuoteDetails = {
companyName: string;
lastTrade: number;
exDividendDate: number;
dividend: number;
high52: number;
week52HiDate: number;
low52: number;
week52LowDate: number;
nextEarningDate: string;
beta: number;
};
export type QuoteData = {
Product: Product;
All: AllQuoteDetails;
};
export type QuoteResponse = {
Messages: Message[];
QuoteResponse: {
QuoteData: QuoteData[];
};
};
/*
* Option chain API
*/
export type OptionDetails = {
inTheMoney: 'y' | 'n';
strikePrice: number;
ask: number;
bid: number;
};
export type OptionChainPair = {
Call: OptionDetails;
Put: OptionDetails;
pairType: 'CALLONLY' | 'PUTONLY' | 'CALLPUT';
};
export type OptionChainResponse = {
OptionChainResponse: {
OptionPair: OptionChainPair[];
};
};
/*
* Account API
*/
export type AccountId = string;
export type Account = {
accountId: AccountId;
accountIdKey: string;
accountMode: 'CASH' | 'MARGIN' | 'CHECKING' | 'IRA' | 'SAVINGS' | 'CD';
accountDesc: string;
accountName: string;
accountType:
| 'AMMCHK'
| 'ARO'
| 'BCHK'
| 'BENFIRA'
| 'BENFROTHIRA'
| 'BENF_ESTATE_IRA'
| 'BENF_MINOR_IRA'
| 'BENF_ROTH_ESTATE_IRA'
| 'BENF_ROTH_MINOR_IRA'
| 'BENF_ROTH_TRUST_IRA'
| 'BENF_TRUST_IRA'
| 'BRKCD'
| 'BROKER'
| 'CASH'
| 'C_CORP'
| 'CONTRIBUTORY'
| 'COVERDELL_ESA'
| 'CONVERSION_ROTH_IRA'
| 'CREDITCARD'
| 'COMM_PROP'
| 'CONSERVATOR'
| 'CORPORATION'
| 'CSA'
| 'CUSTODIAL'
| 'DVP'
| 'ESTATE'
| 'EMPCHK'
| 'EMPMMCA'
| 'ETCHK'
| 'ETMMCHK'
| 'HEIL'
| 'HELOC'
| 'INDCHK'
| 'INDIVIDUAL'
| 'INDIVIDUAL_K'
| 'INVCLUB'
| 'INVCLUB_C_CORP'
| 'INVCLUB_LLC_C_CORP'
| 'INVCLUB_LLC_PARTNERSHIP'
| 'INVCLUB_LLC_S_CORP'
| 'INVCLUB_PARTNERSHIP'
| 'INVCLUB_S_CORP'
| 'INVCLUB_TRUST'
| 'IRA_ROLLOVER'
| 'JOINT'
| 'JTTEN'
| 'JTWROS'
| 'LLC_C_CORP'
| 'LLC_PARTNERSHIP'
| 'LLC_S_CORP'
| 'LLP'
| 'LLP_C_CORP'
| 'LLP_S_CORP'
| 'IRA'
| 'IRACD'
| 'MONEY_PURCHASE'
| 'MARGIN'
| 'MRCHK'
| 'MUTUAL_FUND'
| 'NONCUSTODIAL'
| 'NON_PROFIT'
| 'OTHER'
| 'PARTNER'
| 'PARTNERSHIP'
| 'PARTNERSHIP_C_CORP'
| 'PARTNERSHIP_S_CORP'
| 'PDT_ACCOUNT'
| 'PM_ACCOUNT'
| 'PREFCD'
| 'PREFIRACD'
| 'PROFIT_SHARING'
| 'PROPRIETARY'
| 'REGCD'
| 'ROTHIRA'
| 'ROTH_INDIVIDUAL_K'
| 'ROTH_IRA_MINORS'
| 'SARSEPIRA'
| 'S_CORP'
| 'SEPIRA'
| 'SIMPLE_IRA'
| 'TIC'
| 'TRD_IRA_MINORS'
| 'TRUST'
| 'VARCD'
| 'VARIRACD';
institutionType: 'BROKERAGE';
accountStatus: 'ACTIVE' | 'CLOSED';
closedDate: number;
};
export type AccountListResponse = {
AccountListResponse: {
Accounts: {
Account: Account[];
};
};
};
/*
* Portfolio API
*/
export type Position = {
positionId: number;
Product: Product;
symbolDescription: string;
pricePaid: number;
positionType: 'LONG' | 'SHORT';
};
export type AccountPortfolio = {
accountId: AccountId;
next?: string;
totalPages: number;
nextPageNo?: string;
Position: Position[];
};
export type PortfolioResponse = {
PortfolioResponse: {
AccountPortfolio: AccountPortfolio[];
};
};
// @ts-expect-error Missing types
import OAuth1Client from './OAuth1Client.js';
import chalk from 'chalk';
import session from './session.js';
// @ts-expect-error Missing types
import { open } from './open.js';
import readlineSync from 'readline-sync';
type TokenResponse = {
token: string;
tokenSecret: string;
authorizeUrl: string;
query: string;
};
async function getRequestToken(etradeClient: OAuth1Client): Promise<TokenResponse> {
return etradeClient.requestToken({ oauth_callback: 'oob' });
}
const reqTokenFail = (err: any, etradeClient: OAuth1Client) => {
console.error(
chalk.red(
`Request Token Failed -- Error is ${JSON.stringify(
err,
null,
2
)}\netradeClient is ${JSON.stringify(etradeClient, null, 2)}`
)
);
process.exit(1);
};
const reqTokenSuccess = (resp: TokenResponse) => {
const authorizeUrl = session.getAuthorizeUrl(resp.token);
session.setReqToken(resp.token);
session.setReqTokenSecret(resp.tokenSecret);
open(authorizeUrl);
return readlineSync.question(
'Please accept agreement and enter verification code from browser: '
);
};
export async function authorizeETrade() {
const etradeClient: OAuth1Client = session.createEtradeClient();
// Authorize E*Trade API
if (session.getAccessToken() && session.getAccessTokenSecret()) {
// We have a saved access token, so let's refresh it.
// On second thought, refreshing the token frequently seems to either fail or require
// a new verification code, so let's just use the saved token.
try {
// await session
// .getEtradeClient()
// .refreshToken(session.getAccessToken(), session.getAccessTokenSecret());
session.setAuthClient(
session.getEtradeClient().auth(session.getAccessToken(), session.getAccessTokenSecret())
);
} catch {
session.setAccessToken('');
session.setAccessTokenSecret('');
}
}
if (!session.getAccessToken() || !session.getAccessTokenSecret()) {
try {
const resp = await getRequestToken(etradeClient);
const verificationCode = reqTokenSuccess(resp);
session.setVerifier(verificationCode);
} catch (err: any) {
reqTokenFail(err, etradeClient);
}
try {
const resp = await session
.getEtradeClient()
.accessToken(session.getReqToken(), session.getReqTokenSecret(), session.getVerifier());
session.setAccessToken(resp.token);
session.setAccessTokenSecret(resp.tokenSecret);
session.setAuthClient(session.getEtradeClient().auth(resp.token, resp.tokenSecret));
} catch (err: any) {
console.error(chalk.red(`Access Token Failed -- Error is ${JSON.stringify(err, null, 2)}`));
process.exit(1);
}
}
}
/*
From https://developer.etrade.com/home
BMorearty Apr 8, 2023 Support access token refresh.
BMorearty Apr 2, 2023 Use import and fetch.
*/
/*
=============================================
This code is based on Andrew Smith oauth-1-client library code
with some modification
==============================================
Copyright statement from oauth-1-client code
The MIT License (MIT)
Copyright (c) Andrew Smith
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
*/
import assert from 'node:assert';
import OAuth from 'oauth';
import debugPkg from 'debug';
import querystring from 'querystring';
const { debug: dbg } = debugPkg;
const debug = dbg('oauth-1-client');
class HttpApiError extends Error {
#statusCode;
#body;
constructor(props) {
super(props);
this.#statusCode = props.statusCode;
this.#body = props.body;
}
}
const OAuth1Client = function (options) {
assert(options.key, 'Must provide API key');
assert(options.secret, 'Must provide API secret');
assert(options.requestUrl, 'Must provide requestUrl');
assert(options.accessUrl, 'Must provide accessUrl');
assert(options.apiHostName, 'Must provide apiHostName');
assert(options.refreshUrl, 'Must provide refreshUrl');
this.apiKey = options.key;
this.apiSecret = options.secret;
this.apiHostName = options.apiHostName;
// Note: callbackURL is optional here. Can also be provided via extraParams arg to requestToken
this.callbackURL = options.callbackURL;
this.oauthClient = new OAuth.OAuth(
options.requestUrl,
options.accessUrl,
this.apiKey,
this.apiSecret,
'1.0',
this.callbackURL,
'HMAC-SHA1'
);
// BMorearty
this.oauthClient._refreshUrl = options.refreshUrl;
this.oauthClient._clientOptions.refreshTokenHttpMethod = 'GET';
};
function getCredentials(token, secret, required) {
let credentials;
if (typeof token === 'object') {
credentials = token;
} else {
credentials = {
token,
secret,
};
}
if (required) {
assert(credentials.token, 'Must supply authentication token');
assert(credentials.secret, 'Must supply authentication secret');
}
return credentials;
}
OAuth1Client.prototype.auth = function (token, secret) {
const credentials = getCredentials(token, secret);
const self = this;
return {
get(path, pageOrQuery, extraParams, contentType) {
return self.get(path, pageOrQuery, credentials, null, extraParams, contentType);
},
put(path, content) {
return self.put(path, content, credentials);
},
post(path, content) {
return self.post(path, content, credentials);
},
delete(path) {
return self.delete(path, credentials);
},
};
};
// Patch the interface to allow user to set extra params and the content-type header
OAuth1Client.prototype.get = function (path, pageOrQuery, token, secret, extraParams, contentType) {
const credentials = getCredentials(token, secret);
contentType = typeof contentType === 'undefined' ? 'application/json' : contentType;
extraParams = typeof extraParams === 'undefined' ? null : extraParams;
return new Promise((resolve, reject) => {
const responseHandler = createResponseHandler(resolve, reject);
let url;
if (credentials.token && credentials.secret) {
url = buildUrl(this.apiKey, this.apiSecret, this.apiHostName, path, pageOrQuery);
if (extraParams !== null) {
url += `?${new URLSearchParams(extraParams).toString()}`;
}
debug('GET (auth):', url);
this.oauthClient._performSecureRequest(
credentials.token,
credentials.secret,
'GET',
url,
null,
'',
contentType,
responseHandler
);
} else {
url = buildUrl(this.apiKey, null, this.apiHostName, path, pageOrQuery);
debug('GET (unauth):', url);
fetch(url, {
method: 'GET',
headers: { 'Content-Type': contentType },
// qs: extraParams,
})
.then((res) => {
if (!res.ok) {
responseHandler(res, null, res);
}
const reader = res.body.getReader();
reader.read().then((body) => {
responseHandler(null, body, res);
});
})
.catch((err) => {
responseHandler(err, null, null);
});
}
});
};
OAuth1Client.prototype.put = function (path, content, token, secret) {
const credentials = getCredentials(token, secret, true);
const url = buildUrl(this.apiKey, this.apiSecret, this.apiHostName, path);
return new Promise((resolve, reject) => {
const responseHandler = createResponseHandler(resolve, reject);
debug('PUT:', url);
const contentType = 'application/json';
this.oauthClient._performSecureRequest(
credentials.token,
credentials.secret,
'PUT',
url,
null,
content,
contentType,
responseHandler
);
});
};
OAuth1Client.prototype.post = function (path, content, token, secret) {
const credentials = getCredentials(token, secret, true);
const url = buildUrl(this.apiKey, this.apiSecret, this.apiHostName, path);
return new Promise((resolve, reject) => {
const responseHandler = createResponseHandler(resolve, reject);
debug('POST:', url);
const contentType = 'application/json';
this.oauthClient._performSecureRequest(
credentials.token,
credentials.secret,
'POST',
url,
null,
content,
contentType,
responseHandler
);
});
};
OAuth1Client.prototype.delete = function (path, token, secret) {
const credentials = getCredentials(token, secret, true);
const url = buildUrl(this.apiKey, this.apiSecret, this.apiHostName, path);
return new Promise((resolve, reject) => {
const responseHandler = createResponseHandler(resolve, reject);
debug('DELETE:', url);
const contentType = 'application/json';
this.oauthClient._performSecureRequest(
credentials.token,
credentials.secret,
'DELETE',
url,
null,
content,
contentType,
responseHandler
);
});
};
OAuth1Client.prototype.requestToken = function (extraParams) {
return new Promise((resolve, reject) => {
this.oauthClient.getOAuthRequestToken(
extraParams || {},
(err, oauthToken, oauthTokenSecret, parsedQueryString) => {
if (err) {
return reject(err);
}
resolve({
token: oauthToken,
tokenSecret: oauthTokenSecret,
authorizeUrl: parsedQueryString.login_url,
query: parsedQueryString,
});
}
);
});
};
OAuth1Client.prototype.accessToken = function (token, secret, verifier) {
return new Promise((resolve, reject) => {
this.oauthClient.getOAuthAccessToken(
token,
secret,
verifier,
(err, oauthAccessToken, oauthAccessTokenSecret, parsedQueryString) => {
if (err) {
return reject(err);
}
resolve({
token: oauthAccessToken,
tokenSecret: oauthAccessTokenSecret,
query: parsedQueryString,
});
}
);
});
};
// 4/8/23 BMorearty add ability to refresh access token.
// Copied and modified from getOAuthAccessToken in oauth.js
OAuth.OAuth.prototype.refreshOAuthAccessToken = function (
api_key,
oauth_token,
oauth_token_secret,
callback
) {
var extraParams = {
oauth_consumer_key: api_key,
oauth_token: oauth_token,
};
this._performSecureRequest(
oauth_token,
oauth_token_secret,
this._clientOptions.refreshTokenHttpMethod,
this._refreshUrl,
extraParams,
null,
null,
function (error, data, response) {
if (error) callback(error);
else {
var results = querystring.parse(data);
var oauth_access_token = results['oauth_token'];
delete results['oauth_token'];
var oauth_access_token_secret = results['oauth_token_secret'];
delete results['oauth_token_secret'];
callback(null, oauth_access_token, oauth_access_token_secret, results);
}
}
);
};
// 4/8/23 BMorearty
OAuth1Client.prototype.refreshToken = function (token, secret) {
return new Promise((resolve, reject) => {
this.oauthClient.refreshOAuthAccessToken(
this.apiKey,
token,
secret,
(err, oauthAccessToken, oauthAccessTokenSecret, parsedQueryString) => {
if (err) {
return reject(err);
}
resolve({
token: oauthAccessToken,
tokenSecret: oauthAccessTokenSecret,
query: parsedQueryString,
});
}
);
});
};
function createResponseHandler(resolve, reject) {
return function responseHandler(err, data, res) {
if (err) {
// patch the error returned from oauth lib to add headers (can include important info like request IDs)
if (res && res.headers) {
err.headers = res.headers;
}
return reject(err);
}
if (res.statusCode.toString()[0] !== '2') {
return reject(new HttpApiError({ statusCode: res.statusCode, body: data }));
}
if (typeof data === 'string') {
try {
const parsedBody = JSON.parse(data || '{}');
resolve({
statusCode: res.statusCode,
body: parsedBody,
headers: res.headers,
});
} catch (err) {
reject(`Error parsing JSON response from API. Error:${err}`);
}
}
};
}
function buildUrl(apiKey, apiSecret, apiHostName, path, pageOrQuery) {
if (apiHostName === null) {
throw new Error('Must provide apiHostName');
}
if (path === null) {
throw new Error('Must provide a path');
}
const query = pageOrQuery && typeof pageOrQuery === 'object' ? pageOrQuery : {};
if (apiKey && !apiSecret) {
query.api_key = apiKey;
}
const url = new URL(path, `https://${apiHostName}`);
for (const [key, value] of Object.entries(query)) {
url.searchParams.append(key, value);
}
return url.toString();
}
export default OAuth1Client;
import { exec } from 'child_process';
import { join } from 'path';
/**
* open a file or uri using the default application for the file type.
*
* @return {ChildProcess} - the child process object.
* @param {string} target - the file/uri to open.
* @param {string} appName - (optional) the application to be used to open the
* file (for example, "chrome", "firefox")
* @param {function(Error)} callback - called with null on success, or
* an error object that contains a property 'code' with the exit
* code of the process.
*/
export function open(target, appName, callback) {
var opener;
if (typeof appName === 'function') {
callback = appName;
appName = null;
}
switch (process.platform) {
case 'darwin':
if (appName) {
opener = 'open -a "' + escape(appName) + '"';
} else {
opener = 'open';
}
break;
case 'win32':
// if the first parameter to start is quoted, it uses that as the title
// so we pass a blank title so we can quote the file we are opening
if (appName) {
opener = 'start "" "' + escape(appName) + '"';
} else {
opener = 'start ""';
}
break;
default:
if (appName) {
opener = escape(appName);
} else {
// use Portlands xdg-open everywhere else
opener = join(__dirname, '../vendor/xdg-open');
}
break;
}
if (process.env.SUDO_USER) {
opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener;
}
return exec(opener + ' "' + escape(target) + '"', callback);
}
function escape(s) {
return s.replace(/"/g, '\\"');
}
/*
From https://developer.etrade.com/home
Modified 4-2-2023 by BMorearty to use import and export and TypeScript
*/
import { readFileSync, writeFileSync } from 'node:fs';
// @ts-expect-error Missing types
import OAuth1Client from './OAuth1Client.js';
import { Account as ApiAccount } from './ETradeAPI.js';
export type Account = { accountNickname: string } & ApiAccount;
type AuthClient = {
get(path: string, pageOrQuery?: object, extraParams?: object, contentType?: string): any;
put(
path: string,
content: Buffer | string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer
): any;
post(
path: string,
content: Buffer | string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer
): any;
delete(path: string): any;
};
const session = {
current: { consumerKey: '', consumerSecret: '', baseUrl: '' },
keyType: '' as 'sandbox' | 'live',
authClient: null as AuthClient | null,
verifier: '',
etradeClient: null as OAuth1Client,
openOrderList: [{}] as object[],
sandbox: {
consumerKey: process.env.ETRADE_SANDBOX_API_KEY || '',
consumerSecret: process.env.ETRADE_SANDBOX_API_SECRET || '',
baseUrl: process.env.ETRADE_SANDBOX_BASE_URL || '',
},
live: {
consumerKey: process.env.ETRADE_LIVE_API_KEY || '',
consumerSecret: process.env.ETRADE_LIVE_API_SECRET || '',
baseUrl: process.env.ETRADE_LIVE_BASE_URL || '',
},
oauth: {
authorizeUrl: 'https://us.etrade.com/e/t/etws/authorize',
accessUrl: '/oauth/access_token',
tokenUrl: '/oauth/request_token',
},
order: {
price_type: '',
order_term: '',
symbol: '',
order_action: '',
limit_price: 0,
quantity: '',
},
api: {
accountListPath: '/v1/accounts/list',
balancePath: '/v1/accounts/',
portfolioPath: '/v1/accounts/',
quotePath: '/v1/market/quote/',
accountsPath: '/v1/accounts/',
optionExpireDatePath: '/v1/market/optionexpiredate.json',
optionChainsPath: '/v1/market/optionchains.json',
},
acctList: [] as Account[],
currentAcctIdx: undefined as number | undefined,
quoteUrl: '',
credentials: {} as { token: string; tokenSecret: string },
reqToken: '',
reqTokenSecret: '',
accessToken: '',
accessTokenSecret: '',
setCredentials(token: string, tokenSecret: string) {
this.credentials.token = token;
this.credentials.tokenSecret = tokenSecret;
},
getCredentials() {
return this.credentials;
},
createEtradeClient() {
const key = session.getConsumerKey();
const secret = session.getConsumerSecret();
const baseUrl = session.getBaseUrl();
const hostname = session.getHostName();
session.etradeClient = new OAuth1Client({
key,
secret,
callbackURL: 'oob',
requestUrl: `${baseUrl}/oauth/request_token`,
accessUrl: `${baseUrl}/oauth/access_token`,
refreshUrl: `${baseUrl}/oauth/renew_access_token`,
apiHostName: hostname,
});
return session.etradeClient;
},
getEtradeClient() {
return session.etradeClient;
},
getConsumerKey() {
return session.current !== null ? session.current.consumerKey : null;
},
getConsumerSecret() {
return session.current !== null ? session.current.consumerSecret : null;
},
setVerifier(verifier: string) {
session.verifier = verifier;
session.save();
},
getVerifier() {
if (!session.verifier) {
session.load();
}
return session.verifier;
},
setKeyType(type: 'sandbox' | 'live') {
session.keyType = type;
if (type === 'live') {
session.current = session.live;
} else if (type === 'sandbox') {
session.current = session.sandbox;
} else {
session.current = session.live;
}
},
getKeyType() {
return session.keyType;
},
getBaseUrl() {
return session[session.keyType].baseUrl;
},
getHostName() {
const baseUrl = session.getBaseUrl();
// Chop off the "https://"
return baseUrl.slice(8);
},
getQuotePath() {
return session.api.quotePath;
},
getOptionExpireDatePath() {
return session.api.optionExpireDatePath;
},
getOptionChainsPath() {
return session.api.optionChainsPath;
},
setQuoteUrl(url: string) {
session.quoteUrl = url;
},
getQuoteUrl(url: string) {
return session.quoteUrl;
},
getAcctListPath() {
return `${session.api.accountListPath}.json`;
},
getAuthorizeUrl(reqToken: string) {
return `${session.oauth.authorizeUrl}?key=${session.current?.consumerKey}&token=${reqToken}`;
},
getBalancePath() {
if (this.currentAcctIdx === undefined) {
throw new Error('No account selected');
}
const acct = session.acctList[this.currentAcctIdx];
if (!acct) {
throw new Error('Invalid account selected');
}
return {
url: `${session.api.accountsPath + acct.accountIdKey}/balance.json`,
params: { instType: acct.institutionType, realTimeNAV: 'true' },
};
},
getPortfolioPath() {
if (this.currentAcctIdx === undefined) {
throw new Error('No account selected');
}
const acct = session.acctList[this.currentAcctIdx];
return `${session.api.accountsPath + acct.accountIdKey}/portfolio.json`;
},
getCancelOrderPath() {
if (this.currentAcctIdx === undefined) {
throw new Error('No account selected');
}
const acct = session.acctList[this.currentAcctIdx];
return `${session.api.accountsPath + acct.accountIdKey}/orders/cancel.json`;
},
getPreviewOrderPath() {
if (this.currentAcctIdx === undefined) {
throw new Error('No account selected');
}
const acct = session.acctList[this.currentAcctIdx];
return `${session.api.accountsPath + acct.accountIdKey}/orders/preview.json`;
},
getOrderPath() {
if (this.currentAcctIdx === undefined) {
throw new Error('No account selected');
}
const acct = session.acctList[this.currentAcctIdx];
return `${session.api.accountsPath + acct.accountIdKey}/orders.json`;
},
setAcct(idx: number) {
session.currentAcctIdx = idx;
},
getAcct(idx: number) {
if (!session.currentAcctIdx) {
throw new Error('No account selected');
}
if (typeof idx === 'undefined') return session.acctList[session.currentAcctIdx];
return session.acctList[idx];
},
getAcctIdx() {
return session.currentAcctIdx;
},
setAcctList(list: Account[]) {
session.acctList = list;
},
getAcctListLength() {
return session.acctList.length;
},
setOpenOrderList(list: object[]) {
session.openOrderList = list;
},
getOpenOrderListLength() {
return session.openOrderList.length;
},
setReqToken(token: string) {
session.reqToken = token;
session.save();
},
getReqToken() {
if (!session.reqToken) {
session.load();
}
return session.reqToken;
},
setReqTokenSecret(tokenSecret: string) {
session.reqTokenSecret = tokenSecret;
session.save();
},
getReqTokenSecret() {
if (!session.reqTokenSecret) {
session.load();
}
return session.reqTokenSecret;
},
setAuthClient(client: OAuth1Client) {
session.authClient = client;
},
getAuthClient() {
return session.authClient;
},
setAccessToken(token: string) {
session.accessToken = token;
session.save();
},
getAccessToken() {
if (!session.accessToken) {
session.load();
}
return session.accessToken;
},
setAccessTokenSecret(tokenSecret: string) {
session.accessTokenSecret = tokenSecret;
session.save();
},
getAccessTokenSecret() {
if (!session.accessTokenSecret) {
session.load();
}
return session.accessTokenSecret;
},
save() {
writeFileSync(
`db/session.${session.keyType}.json`,
JSON.stringify(
{ accessToken: session.accessToken, accessTokenSecret: session.accessTokenSecret },
null,
2
),
'utf8'
);
},
load() {
try {
const data = JSON.parse(readFileSync(`db/session.${session.keyType}.json`, 'utf8'));
session.accessToken = data.accessToken;
session.accessTokenSecret = data.accessTokenSecret;
} catch (err) {
// Session not found
}
},
};
export default session;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment