@openeo/js-client 2.2.0 → 2.4.1

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/src/connection.js CHANGED
@@ -51,31 +51,23 @@ class Connection {
51
51
  * Auth Provider cache
52
52
  *
53
53
  * @protected
54
- * @type {?Array.<AuthProvider>}
54
+ * @type {Array.<AuthProvider> | null}
55
55
  */
56
56
  this.authProviderList = null;
57
57
  /**
58
58
  * Current auth provider
59
59
  *
60
60
  * @protected
61
- * @type {?AuthProvider}
61
+ * @type {AuthProvider | null}
62
62
  */
63
63
  this.authProvider = null;
64
64
  /**
65
65
  * Capability cache
66
66
  *
67
67
  * @protected
68
- * @type {?Capabilities}
68
+ * @type {Capabilities | null}
69
69
  */
70
70
  this.capabilitiesObject = null;
71
- /**
72
- * Process cache
73
- *
74
- * @protected
75
- * @type {ProcessRegistry}
76
- */
77
- this.processes = new ProcessRegistry([], Boolean(options.addNamespaceToProcess));
78
- this.processes.listeners.push((...args) => this.emit('processesChanged', ...args));
79
71
  /**
80
72
  * Listeners for events.
81
73
  *
@@ -90,12 +82,21 @@ class Connection {
90
82
  * @type {Options}
91
83
  */
92
84
  this.options = options;
85
+ /**
86
+ * Process cache
87
+ *
88
+ * @protected
89
+ * @type {ProcessRegistry}
90
+ */
91
+ this.processes = new ProcessRegistry([], Boolean(options.addNamespaceToProcess));
92
+ this.processes.listeners.push((...args) => this.emit('processesChanged', ...args));
93
93
  }
94
94
 
95
95
  /**
96
96
  * Initializes the connection by requesting the capabilities.
97
97
  *
98
98
  * @async
99
+ * @protected
99
100
  * @returns {Promise<Capabilities>} Capabilities
100
101
  */
101
102
  async init() {
@@ -104,6 +105,35 @@ class Connection {
104
105
  return this.capabilitiesObject;
105
106
  }
106
107
 
108
+ /**
109
+ * Refresh the cache for processes.
110
+ *
111
+ * @async
112
+ * @protected
113
+ * @returns {Promise}
114
+ */
115
+ async refreshProcessCache() {
116
+ if (this.processes.count() === 0) {
117
+ return;
118
+ }
119
+ let promises = this.processes.namespaces().map(namespace => {
120
+ let fn = () => Promise.resolve();
121
+ if (namespace === 'user') {
122
+ if (!this.isAuthenticated()) {
123
+ fn = () => (this.processes.remove(null, 'user') ? Promise.resolve() : Promise.reject(new Error("Can't clear user processes")));
124
+ }
125
+ else if (this.capabilities().hasFeature('listUserProcesses')) {
126
+ fn = () => this.listUserProcesses();
127
+ }
128
+ }
129
+ else if (this.capabilities().hasFeature('listProcesses')) {
130
+ fn = () => this.listProcesses(namespace);
131
+ }
132
+ return fn().catch(error => console.warn(`Could not update processes for namespace '${namespace}' due to an error: ${error.message}`));
133
+ });
134
+ return await Promise.all(promises);
135
+ }
136
+
107
137
  /**
108
138
  * Returns the URL of the versioned back-end instance currently connected to.
109
139
  *
@@ -260,6 +290,25 @@ class Connection {
260
290
  }
261
291
  }
262
292
 
293
+ /**
294
+ * Normalisation of the namespace to a value that is compatible with the OpenEO specs - EXPERIMENTAL.
295
+ *
296
+ * This is required to support UDP that are shared as public. These can only be executed with providing the full URL
297
+ * (e.g. https://<backend>/processes/<namespace>/<process_id>) as the namespace value in the processing graph. For other
298
+ * parts of the API (such as the listing of the processes, only the name of the namespace is required.
299
+ *
300
+ * This function will extract the short name of the namespace from a shareable URL.
301
+ *
302
+ * @protected
303
+ * @param {?string} namespace - Namespace of the process
304
+ * @returns {?string}
305
+ */
306
+ normalizeNamespace(namespace) {
307
+ // The pattern in https://github.com/Open-EO/openeo-api/pull/348 doesn't include the double colon yet - the regexp may change in the future
308
+ const matches = namespace.match( /^https?:\/\/.*\/processes\/(@?[\w\-.~:]+)\/?/i);
309
+ return matches && matches.length > 1 ? matches[1] : namespace;
310
+ }
311
+
263
312
  /**
264
313
  * List processes available on the back-end.
265
314
  *
@@ -278,7 +327,7 @@ class Connection {
278
327
  if (!namespace) {
279
328
  namespace = 'backend';
280
329
  }
281
- let path = (namespace === 'backend') ? '/processes' : `/processes/${namespace}`;
330
+ let path = (namespace === 'backend') ? '/processes' : `/processes/${this.normalizeNamespace(namespace)}`;
282
331
  let response = await this._get(path);
283
332
 
284
333
  if (!Utils.isObject(response.data) || !Array.isArray(response.data.processes)) {
@@ -310,7 +359,7 @@ class Connection {
310
359
  await this.listProcesses();
311
360
  }
312
361
  else {
313
- let response = await this._get(`/processes/${namespace}/${processId}`);
362
+ let response = await this._get(`/processes/${this.normalizeNamespace(namespace)}/${processId}`);
314
363
  if (!Utils.isObject(response.data) || typeof response.data.id !== 'string') {
315
364
  throw new Error('Invalid response received for process');
316
365
  }
@@ -511,6 +560,8 @@ class Connection {
511
560
  this.authProvider = null;
512
561
  }
513
562
  this.emit('authProviderChanged', this.authProvider);
563
+ // Update process cache on auth changes: https://github.com/Open-EO/openeo-js-client/issues/55
564
+ this.refreshProcessCache();
514
565
  }
515
566
 
516
567
  /**
@@ -566,12 +617,12 @@ class Connection {
566
617
  );
567
618
  }
568
619
 
569
-
570
620
  /**
571
621
  * A callback that is executed on upload progress updates.
572
622
  *
573
623
  * @callback uploadStatusCallback
574
624
  * @param {number} percentCompleted - The percent (0-100) completed.
625
+ * @param {UserFile} file - The file object corresponding to the callback.
575
626
  */
576
627
 
577
628
  /**
@@ -586,15 +637,16 @@ class Connection {
586
637
  * @param {*} source - The source, see method description for details.
587
638
  * @param {?string} [targetPath=null] - The target path on the server, relative to the user workspace. Defaults to the file name of the source file.
588
639
  * @param {?uploadStatusCallback} [statusCallback=null] - Optionally, a callback that is executed on upload progress updates.
640
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the upload process.
589
641
  * @returns {Promise<UserFile>}
590
642
  * @throws {Error}
591
643
  */
592
- async uploadFile(source, targetPath = null, statusCallback = null) {
644
+ async uploadFile(source, targetPath = null, statusCallback = null, abortController = null) {
593
645
  if (targetPath === null) {
594
646
  targetPath = Environment.fileNameForUpload(source);
595
647
  }
596
648
  let file = await this.getFile(targetPath);
597
- return await file.uploadFile(source, statusCallback);
649
+ return await file.uploadFile(source, statusCallback, abortController);
598
650
  }
599
651
 
600
652
  /**
@@ -711,9 +763,10 @@ class Connection {
711
763
  * @param {Process} process - A user-defined process.
712
764
  * @param {?string} [plan=null] - The billing plan to use for this computation.
713
765
  * @param {?number} [budget=null] - The maximum budget allowed to spend for this computation.
766
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the processing request.
714
767
  * @returns {Promise<SyncResult>} - An object with the data and some metadata.
715
768
  */
716
- async computeResult(process, plan = null, budget = null) {
769
+ async computeResult(process, plan = null, budget = null, abortController = null) {
717
770
  let requestBody = this._normalizeUserProcess(
718
771
  process,
719
772
  {
@@ -721,7 +774,7 @@ class Connection {
721
774
  budget: budget
722
775
  }
723
776
  );
724
- let response = await this._post('/result', requestBody, Environment.getResponseType());
777
+ let response = await this._post('/result', requestBody, Environment.getResponseType(), abortController);
725
778
  let syncResult = {
726
779
  data: response.data,
727
780
  costs: null,
@@ -772,10 +825,11 @@ class Connection {
772
825
  * @param {string} targetPath - The target, see method description for details.
773
826
  * @param {?string} [plan=null] - The billing plan to use for this computation.
774
827
  * @param {?number} [budget=null] - The maximum budget allowed to spend for this computation.
828
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the processing request.
775
829
  * @throws {Error}
776
830
  */
777
- async downloadResult(process, targetPath, plan = null, budget = null) {
778
- let response = await this.computeResult(process, plan, budget);
831
+ async downloadResult(process, targetPath, plan = null, budget = null, abortController = null) {
832
+ let response = await this.computeResult(process, plan, budget, abortController);
779
833
  // @ts-ignore
780
834
  await Environment.saveToFile(response.data, targetPath);
781
835
  }
@@ -820,7 +874,7 @@ class Connection {
820
874
  throw new Error("Response did not contain a Job ID. Job has likely been created, but may not show up yet.");
821
875
  }
822
876
  let job = new Job(this, response.headers['openeo-identifier']).setAll(requestBody);
823
- if (this.capabilitiesObject.hasFeature('describeJob')) {
877
+ if (this.capabilities().hasFeature('describeJob')) {
824
878
  return await job.describeJob();
825
879
  }
826
880
  else {
@@ -886,7 +940,7 @@ class Connection {
886
940
  throw new Error("Response did not contain a Service ID. Service has likely been created, but may not show up yet.");
887
941
  }
888
942
  let service = new Service(this, response.headers['openeo-identifier']).setAll(requestBody);
889
- if (this.capabilitiesObject.hasFeature('describeService')) {
943
+ if (this.capabilities().hasFeature('describeService')) {
890
944
  return service.describeService();
891
945
  }
892
946
  else {
@@ -955,17 +1009,19 @@ class Connection {
955
1009
  * @param {string} path
956
1010
  * @param {*} body
957
1011
  * @param {string} responseType - Response type according to axios, defaults to `json`.
1012
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the request.
958
1013
  * @returns {Promise<AxiosResponse>}
959
1014
  * @throws {Error}
960
1015
  * @see https://github.com/axios/axios#request-config
961
1016
  */
962
- async _post(path, body, responseType) {
963
- return await this._send({
1017
+ async _post(path, body, responseType, abortController = null) {
1018
+ let options = {
964
1019
  method: 'post',
965
1020
  responseType: responseType,
966
1021
  url: path,
967
1022
  data: body
968
- });
1023
+ };
1024
+ return await this._send(options, abortController);
969
1025
  }
970
1026
 
971
1027
  /**
@@ -1051,11 +1107,12 @@ class Connection {
1051
1107
  *
1052
1108
  * @async
1053
1109
  * @param {object.<string, *>} options
1110
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the request.
1054
1111
  * @returns {Promise<AxiosResponse>}
1055
1112
  * @throws {Error}
1056
1113
  * @see https://github.com/axios/axios
1057
1114
  */
1058
- async _send(options) {
1115
+ async _send(options, abortController = null) {
1059
1116
  options.baseURL = this.baseUrl;
1060
1117
  if (this.isAuthenticated() && (typeof options.authorization === 'undefined' || options.authorization === true)) {
1061
1118
  if (!options.headers) {
@@ -1066,6 +1123,9 @@ class Connection {
1066
1123
  if (!options.responseType) {
1067
1124
  options.responseType = 'json';
1068
1125
  }
1126
+ if (abortController) {
1127
+ options.signal = abortController.signal;
1128
+ }
1069
1129
 
1070
1130
  try {
1071
1131
  return await axios(options);
package/src/job.js CHANGED
@@ -44,7 +44,7 @@ class Job extends BaseEntity {
44
44
  * The process chain to be executed.
45
45
  * @public
46
46
  * @readonly
47
- * @type {Process}
47
+ * @type {?Process}
48
48
  */
49
49
  this.process = undefined;
50
50
  /**
@@ -52,35 +52,35 @@ class Job extends BaseEntity {
52
52
  * One of "created", "queued", "running", "canceled", "finished" or "error".
53
53
  * @public
54
54
  * @readonly
55
- * @type {string}
55
+ * @type {?string}
56
56
  */
57
57
  this.status = undefined;
58
58
  /**
59
59
  * Indicates the process of a running batch job in percent.
60
60
  * @public
61
61
  * @readonly
62
- * @type {number}
62
+ * @type {?number}
63
63
  */
64
64
  this.progress = undefined;
65
65
  /**
66
66
  * Date and time of creation, formatted as a RFC 3339 date-time.
67
67
  * @public
68
68
  * @readonly
69
- * @type {string}
69
+ * @type {?string}
70
70
  */
71
71
  this.created = undefined;
72
72
  /**
73
73
  * Date and time of the last status change, formatted as a RFC 3339 date-time.
74
74
  * @public
75
75
  * @readonly
76
- * @type {string}
76
+ * @type {?string}
77
77
  */
78
78
  this.updated = undefined;
79
79
  /**
80
80
  * The billing plan to process and charge the batch job with.
81
81
  * @public
82
82
  * @readonly
83
- * @type {string}
83
+ * @type {?string}
84
84
  */
85
85
  this.plan = undefined;
86
86
  /**
@@ -78,7 +78,7 @@ class OidcProvider extends AuthProvider {
78
78
  /**
79
79
  * The client ID to use for authentication.
80
80
  *
81
- * @type {?string}
81
+ * @type {string | null}
82
82
  */
83
83
  this.clientId = null;
84
84
 
@@ -105,6 +105,13 @@ class OidcProvider extends AuthProvider {
105
105
  */
106
106
  this.scopes = Array.isArray(options.scopes) && options.scopes.length > 0 ? options.scopes : ['openid'];
107
107
 
108
+ /**
109
+ * The scope that is used to request a refresh token.
110
+ *
111
+ * @type {string}
112
+ */
113
+ this.refreshTokenScope = "offline_access";
114
+
108
115
  /**
109
116
  * Any additional links.
110
117
  *
@@ -132,8 +139,8 @@ class OidcProvider extends AuthProvider {
132
139
  * Adds a listener to one of the following events:
133
140
  *
134
141
  * - AccessTokenExpiring: Raised prior to the access token expiring.
135
- * - accessTokenExpired: Raised after the access token has expired.
136
- * - silentRenewError: Raised when the automatic silent renew has failed.
142
+ * - AccessTokenExpired: Raised after the access token has expired.
143
+ * - SilentRenewError: Raised when the automatic silent renew has failed.
137
144
  *
138
145
  * @param {string} event
139
146
  * @param {Function} callback
@@ -161,17 +168,20 @@ class OidcProvider extends AuthProvider {
161
168
  *
162
169
  * Supported only in Browser environments.
163
170
  *
171
+ * @async
164
172
  * @param {object.<string, *>} [options={}] - Object with authentication options.
173
+ * @param {boolean} [requestRefreshToken=false] - If set to `true`, adds a scope to request a refresh token.
165
174
  * @returns {Promise<void>}
166
175
  * @throws {Error}
167
176
  * @see https://github.com/IdentityModel/oidc-client-js/wiki#other-optional-settings
177
+ * @see {OidcProvider#refreshTokenScope}
168
178
  */
169
- async login(options = {}) {
179
+ async login(options = {}, requestRefreshToken = false) {
170
180
  if (!this.issuer || typeof this.issuer !== 'string') {
171
181
  throw new Error("No Issuer URL available for OpenID Connect");
172
182
  }
173
183
 
174
- this.manager = new Oidc.UserManager(this.getOptions(options));
184
+ this.manager = new Oidc.UserManager(this.getOptions(options, requestRefreshToken));
175
185
  this.addListener('UserLoaded', async () => this.setUser(await this.manager.getUser()), 'js-client');
176
186
  this.addListener('AccessTokenExpired', () => this.setUser(null), 'js-client');
177
187
  if (OidcProvider.uiMethod === 'popup') {
@@ -216,15 +226,22 @@ class OidcProvider extends AuthProvider {
216
226
  *
217
227
  * @protected
218
228
  * @param {object.<string, *>} options
229
+ * @param {boolean} [requestRefreshToken=false] - If set to `true`, adds a scope to request a refresh token.
219
230
  * @returns {object.<string, *>}
231
+ * @see {OidcProvider#refreshTokenScope}
220
232
  */
221
- getOptions(options = {}) {
233
+ getOptions(options = {}, requestRefreshToken = false) {
222
234
  let response_type = this.getResponseType();
235
+ let scope = this.scopes.slice(0);
236
+ if (requestRefreshToken && !scope.includes(this.refreshTokenScope)) {
237
+ scope.push(this.refreshTokenScope);
238
+ }
239
+
223
240
  return Object.assign({
224
241
  client_id: this.clientId,
225
242
  redirect_uri: OidcProvider.redirectUrl,
226
243
  authority: this.issuer.replace('/.well-known/openid-configuration', ''),
227
- scope: this.scopes.join(' '),
244
+ scope: scope.join(' '),
228
245
  validateSubOnSilentRenew: true,
229
246
  response_type,
230
247
  response_mode: response_type.includes('code') ? 'query' : 'fragment'
package/src/openeo.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const axios = require('axios').default;
2
+ const { AbortController } = require("node-abort-controller");
2
3
  const Utils = require('@openeo/js-commons/src/utils');
3
4
  const Versions = require('@openeo/js-commons/src/versions');
4
5
 
@@ -109,7 +110,7 @@ class OpenEO {
109
110
  * @returns {string} Version number (according to SemVer).
110
111
  */
111
112
  static clientVersion() {
112
- return "2.2.0";
113
+ return "2.4.1";
113
114
  }
114
115
 
115
116
  }
@@ -117,6 +118,7 @@ class OpenEO {
117
118
  OpenEO.Environment = require('./env');
118
119
 
119
120
  module.exports = {
121
+ AbortController,
120
122
  AuthProvider,
121
123
  BasicProvider,
122
124
  Capabilities,
package/src/service.js CHANGED
@@ -39,55 +39,55 @@ class Service extends BaseEntity {
39
39
  * The process chain to be executed.
40
40
  * @public
41
41
  * @readonly
42
- * @type {Process}
42
+ * @type {?Process}
43
43
  */
44
44
  this.process = undefined;
45
45
  /**
46
46
  * URL at which the secondary web service is accessible
47
47
  * @public
48
48
  * @readonly
49
- * @type {string}
49
+ * @type {?string}
50
50
  */
51
51
  this.url = undefined;
52
52
  /**
53
53
  * Web service type (protocol / standard) that is exposed.
54
54
  * @public
55
55
  * @readonly
56
- * @type {string}
56
+ * @type {?string}
57
57
  */
58
58
  this.type = undefined;
59
59
  /**
60
60
  * @public
61
61
  * @readonly
62
- * @type {boolean}
62
+ * @type {?boolean}
63
63
  */
64
64
  this.enabled = undefined;
65
65
  /**
66
66
  * Map of configuration settings, i.e. the setting names supported by the secondary web service combined with actual values.
67
67
  * @public
68
68
  * @readonly
69
- * @type {object.<string, *>}
69
+ * @type {?object.<string, *>}
70
70
  */
71
71
  this.configuration = undefined;
72
72
  /**
73
73
  * Additional attributes of the secondary web service, e.g. available layers for a WMS based on the bands in the underlying GeoTiff.
74
74
  * @public
75
75
  * @readonly
76
- * @type {object.<string, *>}
76
+ * @type {?object.<string, *>}
77
77
  */
78
78
  this.attributes = undefined;
79
79
  /**
80
80
  * Date and time of creation, formatted as a RFC 3339 date-time.
81
81
  * @public
82
82
  * @readonly
83
- * @type {string}
83
+ * @type {?string}
84
84
  */
85
85
  this.created = undefined;
86
86
  /**
87
87
  * The billing plan to process and charge the service with.
88
88
  * @public
89
89
  * @readonly
90
- * @type {string}
90
+ * @type {?string}
91
91
  */
92
92
  this.plan = undefined;
93
93
  /**
package/src/userfile.js CHANGED
@@ -90,10 +90,11 @@ class UserFile extends BaseEntity {
90
90
  * @async
91
91
  * @param {*} source - The source, see method description for details.
92
92
  * @param {?uploadStatusCallback} statusCallback - Optionally, a callback that is executed on upload progress updates.
93
+ * @param {?AbortController} [abortController=null] - An AbortController object that can be used to cancel the upload process.
93
94
  * @returns {Promise<UserFile>}
94
95
  * @throws {Error}
95
96
  */
96
- async uploadFile(source, statusCallback = null) {
97
+ async uploadFile(source, statusCallback = null, abortController = null) {
97
98
  let options = {
98
99
  method: 'put',
99
100
  url: '/files/' + this.path,
@@ -109,7 +110,7 @@ class UserFile extends BaseEntity {
109
110
  };
110
111
  }
111
112
 
112
- let response = await this.connection._send(options);
113
+ let response = await this.connection._send(options, abortController);
113
114
  return this.setAll(response.data);
114
115
  }
115
116
 
@@ -52,7 +52,7 @@ class UserProcess extends BaseEntity {
52
52
  * A list of categories.
53
53
  * @public
54
54
  * @readonly
55
- * @type {Array.<string>}
55
+ * @type {?Array.<string>}
56
56
  */
57
57
  this.categories = undefined;
58
58
  /**
@@ -74,40 +74,40 @@ class UserProcess extends BaseEntity {
74
74
  * Specifies that the process or parameter is deprecated with the potential to be removed in any of the next versions.
75
75
  * @public
76
76
  * @readonly
77
- * @type {boolean}
77
+ * @type {?boolean}
78
78
  */
79
79
  this.deprecated = undefined;
80
80
  /**
81
81
  * Declares the process or parameter to be experimental, which means that it is likely to change or may produce unpredictable behaviour.
82
82
  * @public
83
83
  * @readonly
84
- * @type {boolean}
84
+ * @type {?boolean}
85
85
  */
86
86
  this.experimental = undefined;
87
87
  /**
88
88
  * Declares any exceptions (errors) that might occur during execution of this process.
89
89
  * @public
90
90
  * @readonly
91
- * @type {object.<string, *>}
91
+ * @type {?object.<string, *>}
92
92
  */
93
93
  this.exceptions = undefined;
94
94
  /**
95
95
  * @public
96
96
  * @readonly
97
- * @type {Array.<object.<string, *>>}
97
+ * @type {?Array.<object.<string, *>>}
98
98
  */
99
99
  this.examples = undefined;
100
100
  /**
101
101
  * Links related to this process.
102
102
  * @public
103
103
  * @readonly
104
- * @type {Array.<Link>}
104
+ * @type {?Array.<Link>}
105
105
  */
106
106
  this.links = undefined;
107
107
  /**
108
108
  * @public
109
109
  * @readonly
110
- * @type {object.<string, *>}
110
+ * @type {?object.<string, *>}
111
111
  */
112
112
  this.processGraph = undefined;
113
113
  }