@barchart/portfolio-client-js 1.4.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ const assert = require('@barchart/common-js/lang/assert'),
2
+ Disposable = require('@barchart/common-js/lang/Disposable'),
3
+ is = require('@barchart/common-js/lang/is'),
4
+ random = require('@barchart/common-js/lang/random'),
5
+ Scheduler = require('@barchart/common-js/timing/Scheduler');
6
+
7
+ const EndpointBuilder = require('@barchart/common-js/api/http/builders/EndpointBuilder'),
8
+ Gateway = require('@barchart/common-js/api/http/Gateway'),
9
+ ProtocolType = require('@barchart/common-js/api/http/definitions/ProtocolType'),
10
+ ResponseInterceptor = require('@barchart/common-js/api/http/interceptors/ResponseInterceptor'),
11
+ VerbType = require('@barchart/common-js/api/http/definitions/VerbType');
12
+
13
+ const Configuration = require('../common/Configuration');
14
+
15
+ module.exports = (() => {
16
+ 'use strict';
17
+
18
+ const DEFAULT_REFRESH_INTERVAL_MILLISECONDS = 5 * 60 * 1000;
19
+
20
+ /**
21
+ * Generates and caches a signed token (using a delegate). The cached token
22
+ * is refreshed periodically.
23
+ *
24
+ * @public
25
+ * @exported
26
+ * @param {Callbacks.JwtTokenGenerator} tokenGenerator - An anonymous function which returns a signed JWT token.
27
+ * @param {Number=} refreshInterval - The number of milliseconds which must pass before a new JWT token is generated. A zero value means the token should never be refreshed. A null or undefined value means the token is not cached.
28
+ */
29
+ class JwtProvider extends Disposable {
30
+ constructor(tokenGenerator, refreshInterval) {
31
+ super();
32
+
33
+ assert.argumentIsRequired(tokenGenerator, 'tokenGenerator', Function);
34
+ assert.argumentIsOptional(refreshInterval, 'refreshInterval', Number);
35
+
36
+ this._tokenGenerator = tokenGenerator;
37
+
38
+ this._tokenPromise = null;
39
+
40
+ this._refreshTimestamp = null;
41
+ this._refreshPending = false;
42
+
43
+ if (is.number(refreshInterval)) {
44
+ this._refreshInterval = Math.max(refreshInterval || 0, 0);
45
+ this._refreshJitter = random.range(0, Math.floor(this._refreshInterval / 10));
46
+ } else {
47
+ this._refreshInterval = null;
48
+ this._refreshJitter = null;
49
+ }
50
+
51
+ this._scheduler = new Scheduler();
52
+ }
53
+
54
+ /**
55
+ * Reads the current token, refreshing if necessary.
56
+ *
57
+ * @public
58
+ * @returns {Promise<String>}
59
+ */
60
+ getToken() {
61
+ return Promise.resolve()
62
+ .then(() => {
63
+ if (this._refreshPending) {
64
+ return this._tokenPromise;
65
+ }
66
+
67
+ if (this._tokenPromise === null || this._refreshInterval === null || (this._refreshInterval > 0 && getTime() > (this._refreshTimestamp + this._refreshInterval + this._refreshJitter))) {
68
+ this._refreshPending = true;
69
+
70
+ this._tokenPromise = this._scheduler.backoff(() => this._tokenGenerator(), 100, 'Read JWT token', 3)
71
+ .then((token) => {
72
+ this._refreshTimestamp = getTime();
73
+ this._refreshPending = false;
74
+
75
+ return token;
76
+ }).catch((e) => {
77
+ this._tokenPromise = null;
78
+
79
+ this._refreshTimestamp = null;
80
+ this._refreshPending = false;
81
+
82
+ return Promise.reject(e);
83
+ });
84
+ }
85
+
86
+ return this._tokenPromise;
87
+ });
88
+ }
89
+
90
+ /**
91
+ * A factory for {@link JwtProvider} which is an alternative to the constructor.
92
+ *
93
+ * @public
94
+ * @static
95
+ * @exported
96
+ * @param {Callbacks.JwtTokenGenerator} tokenGenerator - An anonymous function which returns a signed JWT token.
97
+ * @param {Number=} refreshInterval - The number of milliseconds which must pass before a new JWT token is generated. A zero value means the token should never be refreshed. A null or undefined value means the token is not cached.
98
+ * @returns {JwtProvider}
99
+ */
100
+ static fromTokenGenerator(tokenGenerator, refreshInterval) {
101
+ return new JwtProvider(tokenGenerator, refreshInterval);
102
+ }
103
+
104
+ /**
105
+ * Builds a {@link JwtProvider} which will generate tokens impersonating the specified
106
+ * user. These tokens will only work in the "test" environment.
107
+ *
108
+ * Recall, the "test" environment is not "secure" -- any data saved here can be accessed
109
+ * by anyone (using this feature). Furthermore, data is periodically purged from the
110
+ * test environment.
111
+ *
112
+ * @public
113
+ * @static
114
+ * @param {String} userId - The user identifier to impersonate.
115
+ * @param {String} contextId - The context identifier of the user to impersonate.
116
+ * @param {String=} permissions - The desired permission level.
117
+ * @returns {JwtProvider}
118
+ */
119
+ static forTest(userId, contextId, permissions) {
120
+ return getJwtProviderForImpersonation(Configuration.getJwtImpersonationHost, 'test', userId, contextId, permissions);
121
+ }
122
+
123
+ /**
124
+ * Builds a {@link JwtProvider} which will generate tokens impersonating the specified
125
+ * user. The "development" environment is for Barchart use only and access is restricted
126
+ * to Barchart's internal network.
127
+ *
128
+ * @public
129
+ * @static
130
+ * @param {String} userId - The user identifier to impersonate.
131
+ * @param {String} contextId - The context identifier of the user to impersonate.
132
+ * @param {String=} permissions - The desired permission level.
133
+ * @returns {JwtProvider}
134
+ */
135
+ static forDevelopment(userId, contextId, permissions) {
136
+ return getJwtProviderForImpersonation(Configuration.getJwtImpersonationHost, 'dev', userId, contextId, permissions);
137
+ }
138
+
139
+ _onDispose() {
140
+ this._scheduler.dispose();
141
+ this._scheduler = null;
142
+ }
143
+
144
+ toString() {
145
+ return '[JwtProvider]';
146
+ }
147
+ }
148
+
149
+ function getJwtProviderForImpersonation(host, environment, userId, contextId, permissions) {
150
+ assert.argumentIsRequired(host, 'host', String);
151
+ assert.argumentIsRequired(environment, 'environment', String);
152
+ assert.argumentIsRequired(userId, 'userId', String);
153
+ assert.argumentIsRequired(contextId, 'contextId', String);
154
+ assert.argumentIsOptional(permissions, 'permissions', String);
155
+
156
+ const tokenEndpoint = EndpointBuilder.for('generate-impersonation-jwt-for-test', 'generate JWT token for test environment')
157
+ .withVerb(VerbType.POST)
158
+ .withProtocol(ProtocolType.HTTPS)
159
+ .withHost(host)
160
+ .withPathBuilder((pb) =>
161
+ pb.withLiteralParameter('version', 'v1')
162
+ .withLiteralParameter('tokens', 'tokens')
163
+ .withLiteralParameter('impersonate', 'impersonate')
164
+ .withLiteralParameter('service', 'portfolio')
165
+ .withLiteralParameter('environment', environment)
166
+ )
167
+ .withBody()
168
+ .withResponseInterceptor(ResponseInterceptor.DATA)
169
+ .endpoint;
170
+
171
+ const payload = { };
172
+
173
+ payload.userId = userId;
174
+ payload.contextId = contextId;
175
+
176
+ if (permissions) {
177
+ payload.permissions = permissions;
178
+ }
179
+
180
+ return new JwtProvider(() => Gateway.invoke(tokenEndpoint, payload), DEFAULT_REFRESH_INTERVAL_MILLISECONDS);
181
+ }
182
+
183
+ function getTime() {
184
+ return (new Date()).getTime();
185
+ }
186
+
187
+ return JwtProvider;
188
+ })();
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A meta namespace containing signatures of anonymous functions.
3
+ *
4
+ * @namespace Callbacks
5
+ */
6
+
7
+ /**
8
+ * A function which returns a signed token.
9
+ *
10
+ * @public
11
+ * @callback JwtTokenGenerator
12
+ * @memberOf Callbacks
13
+ * @returns {String|Promise<String>}
14
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-client-js",
3
- "version": "1.4.7",
3
+ "version": "2.0.0",
4
4
  "description": "JavaScript library for interfacing with Barchart's Portfolio API",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -21,13 +21,13 @@
21
21
  "SDK"
22
22
  ],
23
23
  "dependencies": {
24
- "@barchart/common-js": "^3.5.1",
24
+ "@barchart/common-js": "^3.15.0",
25
25
  "@barchart/portfolio-api-common": "^1.4.4"
26
26
  },
27
27
  "devDependencies": {
28
- "@babel/core": "^7.6.2",
28
+ "@babel/core": "^7.11.1",
29
29
  "babelify": "^10.0.0",
30
- "browserify": "^16.5.0",
30
+ "browserify": "^16.5.2",
31
31
  "git-get-status": "^1.0.5",
32
32
  "glob": "^6.0.1",
33
33
  "gulp": "^4.0.2",
@@ -35,9 +35,9 @@
35
35
  "gulp-jasmine": "^2.2.1",
36
36
  "gulp-jsdoc3": "^1.0.1",
37
37
  "gulp-jshint": "~2.1.0",
38
- "gulp-replace": "^0.5.4",
39
38
  "gulp-prompt": "^1.2.0",
40
- "jshint": "^2.10.3",
39
+ "gulp-replace": "^0.5.4",
40
+ "jshint": "^2.12.0",
41
41
  "vinyl-buffer": "^1.0.1",
42
42
  "vinyl-source-stream": "^2.0.0"
43
43
  },
@@ -1,124 +0,0 @@
1
- body {
2
- font-family: "Gill Sans MT";
3
- }
4
- h4 {
5
- margin: 0px;
6
- }
7
-
8
- .container-center-outer {
9
- height: 100%;
10
- width: 100%;
11
-
12
- display: table;
13
- }
14
- .container-center-inner {
15
- display: table-cell;
16
- text-align: center;
17
- vertical-align: middle;
18
- }
19
-
20
- .center {
21
- text-align: center;
22
- }
23
-
24
- .header {
25
- position: absolute;
26
- z-index: 1000;
27
- top: 0px;
28
- left: 0px;
29
- right: 0px;
30
- height: 50px;
31
- background-color: #29506D;
32
- color: #FFFFFF;
33
- padding-left: 20px;
34
- padding-right: 20px;
35
- }
36
- .header h4 {
37
- line-height: 50px;
38
- margin-top: 0px;
39
- margin-bottom: 0px;
40
- }
41
- .header .header-action-buttons {
42
- margin-left: 20px;
43
- }
44
- .header .text-button {
45
- line-height: 50px;
46
- margin-right: 10px;
47
- }
48
-
49
- .header input {
50
- position: relative;
51
- top: -4px;
52
- }
53
-
54
- .header .input-group {
55
- position: relative;
56
- top: 6px;
57
- }
58
-
59
- .header .input-group input {
60
- top: 1px;
61
- }
62
-
63
- .header .header-action-buttons .btn-group {
64
- margin-top: 8px;
65
- }
66
-
67
- .main {
68
- position: absolute;
69
- height: auto;
70
- top: 50px;
71
- bottom: 40px;
72
- left: 0px;
73
- right: 0px;
74
- overflow: auto;
75
- padding-top: 20px;
76
- padding-left: 20px;
77
- padding-right: 20px;
78
- }
79
-
80
- .main .login {
81
- margin: auto;
82
- width: 300px;
83
- }
84
-
85
- .footer {
86
- position: absolute;
87
- bottom: 0px;
88
- left: 0px;
89
- right: 0px;
90
- height: 40px;
91
- background-color: #496D89;
92
- opacity: 0.99;
93
- z-index: 1;
94
- padding-left: 20px;
95
- padding-right: 20px;
96
- }
97
- .footer h4 {
98
- font-size: 14px;
99
- line-height: 40px;
100
- margin-top: 0px;
101
- margin-bottom: 0px;
102
- color: white;
103
- }
104
-
105
- .watchlist-action-buttons .text-button {
106
- margin-left: 10px;
107
- }
108
-
109
- .text-button {
110
- cursor: pointer;
111
- }
112
- .text-button:hover {
113
- text-shadow: 0 0 1.2em #00FFFF;
114
- }
115
- .text-button.text-button-black {
116
- color: #555555;
117
- }
118
- .text-button.text-button-black:hover {
119
- text-shadow: 0 0 1.2em #FF0000;
120
- }
121
-
122
- .btn {
123
- text-shadow: none;
124
- }
@@ -1,98 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8"/>
5
-
6
- <title>Barchart Portfolio Client API</title>
7
-
8
- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
9
- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
10
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" type="text/css">
11
- <link rel="stylesheet" href="example.css" type="text/css">
12
-
13
- <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.23.0/polyfill.min.js"></script>
14
-
15
- <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
16
- <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
17
- <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
18
- <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
19
-
20
- <script src="example.js"></script>
21
-
22
- <script type="text/html" id="disconnected-template">
23
- <div class="container-center-outer">
24
- <div class="container-center-inner">
25
- <form class="form-horizontal login">
26
- <div class="form-group" data-bind="css: { 'has-error': user().length === 0 }, event: { keypress: handleLoginKeypress }">
27
- <label class="pull-left">User</label>
28
- <input class="form-control" data-bind="textInput: user" type="text" placeholder="User">
29
- </div>
30
- <div class="form-group" data-bind="css: { 'has-error': user().length === 0 }, event: { keypress: handleLoginKeypress }">
31
- <label class="pull-left">Legacy User</label>
32
- <input class="form-control" data-bind="textInput: userLegacy" type="text" placeholder="Legacy User">
33
- </div>
34
- <div class="form-group buttons">
35
- <button class="form-control btn btn-primary" type="button" data-bind="click: connect, enable: canConnect">Connect</button>
36
- </div>
37
- </form>
38
- </div>
39
- </div>
40
- </script>
41
-
42
- <script type="text/html" id="portfolio-header-template">
43
- <div class="pull-left">
44
- <h4 class="pull-left">Barchart Portfolio API <span data-bind="visible: connected"></span></h4>
45
-
46
- <div class="pull-left header-action-buttons form-inline" style="margin-top: 12px;" data-bind="visible: connected() === true">
47
- <input class="form-control" data-bind="value: portfolio" type="text" placeholder="Portfolio">
48
- <input class="form-control" data-bind="value: position" type="text" placeholder="Position">
49
- <input class="form-control" data-bind="value: transaction" type="text" placeholder="Transaction">
50
- </div>
51
- </div>
52
-
53
- <div class="pull-left">
54
- <div class="pull-left header-action-buttons form-inline" data-bind="visible: connected() === true">
55
- <div class="dropdown" style="margin-top: 8px;">
56
- <button class="btn btn-default dropdown-toggle" style="width: 200px;" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
57
- <span data-bind="text: mode().text"></span>
58
- </button>
59
- <ul class="dropdown-menu" data-bind="foreach: modes">
60
- <li class="center"><a href="#" data-bind="text: $data.text, click: function() { $parent.setMode($data); }"></a></li>
61
- </ul>
62
- </div>
63
- </div>
64
-
65
- <div class="pull-left header-action-buttons form-inline" data-bind="visible: connected() === true">
66
- <span class="text-button glyphicon glyphicon-play" data-bind="click: execute"></span>
67
- </div>
68
- </div>
69
-
70
- <div class="pull-right">
71
- <div class="header-action-buttons pull-right form-inline" data-bind="visible: connected() === true">
72
- <span class="text-button glyphicon glyphicon-remove" data-bind="click: disconnect"></span>
73
- </div>
74
- </div>
75
- </script>
76
-
77
- <script type="text/html" id="portfolio-console-template">
78
- <div data-bind="foreach: console">
79
- <pre data-bind="text: $data" style="font-size: 10px;"></pre>
80
- </div>
81
- </script>
82
-
83
- <script type="text/html" id="footer-template">
84
- <h4 class="pull-left" data-bind="visible: connected">
85
- Client Version: <span data-bind="text: clientVersion"></span>
86
- </h4>
87
- <h4 class="pull-right">
88
- User: <span data-bind="text: user"></span>
89
- </h4>
90
- </script>
91
- </head>
92
- <body>
93
- <div class="header" data-bind="template: { name: 'portfolio-header-template' }"></div>
94
- <div class="main" data-bind="template: { name: activeTemplate }"></div>
95
- <div class="footer" data-bind="template: { name: 'footer-template' }">
96
- </div>
97
- </body>
98
- </html>