├── .eslintrc.json
├── .github
└── workflows
│ ├── docs-latest.yml
│ ├── docs.yml
│ └── tests.yml
├── .gitignore
├── .jsdoc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── examples
├── node
│ ├── discovery.js
│ └── package.json
├── oidc
│ ├── openid-connect-popup.html
│ └── openid-connect-redirect.html
├── typescript
│ ├── .gitignore
│ ├── discovery.ts
│ └── package.json
└── web
│ ├── discovery.html
│ └── workflow.html
├── jest.config.js
├── jsconfig.json
├── openeo.d.ts
├── package.json
├── src
├── authprovider.js
├── baseentity.js
├── basicprovider.js
├── browser.js
├── builder
│ ├── builder.js
│ ├── formula.js
│ ├── node.js
│ ├── parameter.js
│ └── tapdigit.js
├── capabilities.js
├── connection.js
├── env.js
├── filetypes.js
├── job.js
├── logs.js
├── node.js
├── oidcprovider.js
├── openeo.js
├── pages.js
├── service.js
├── typedefs.js
├── userfile.js
└── userprocess.js
├── tests
├── basic.test.js
├── builder.array_create.test.js
├── builder.evi.test.js
├── builder.misc.test.js
├── builder.namespaces.test.js
├── builder.s1.test.js
├── config.js
├── data
│ ├── builder.array_create.example.json
│ ├── builder.evi.example.json
│ ├── builder.math.evi.example.json
│ ├── builder.s1.example.json
│ └── gee
│ │ ├── collection.json
│ │ ├── process.json
│ │ └── processgraph.json
├── earthengine.test.js
├── eodc.test.js
└── vito.test.js
├── tsconfig.json
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "node": true,
6 | "es6": true
7 | },
8 | "extends": [
9 | "eslint:recommended"
10 | ],
11 | "parserOptions": {
12 | "ecmaVersion": 9
13 | },
14 | "plugins": [
15 | "jsdoc"
16 | ],
17 | "rules": {
18 | "no-constant-condition": ["error", { "checkLoops": false }],
19 | "no-template-curly-in-string": "error",
20 | "block-scoped-var": "error",
21 | "no-unreachable-loop": "error",
22 | "require-atomic-updates": "error",
23 | "max-classes-per-file": "error",
24 | "no-alert": "error",
25 | "no-caller": "error",
26 | "eqeqeq": "error",
27 | "no-constructor-return": "error",
28 | "no-eval": "error",
29 | "no-extend-native": "error",
30 | "no-extra-bind": "error",
31 | "no-implicit-coercion": "error",
32 | "no-invalid-this": "error",
33 | "no-multi-spaces": "error",
34 | "no-multi-str": "error",
35 | "no-return-assign": "error",
36 | "no-self-compare": "error",
37 | "no-sequences": "error",
38 | "no-throw-literal": "error",
39 | "no-unmodified-loop-condition": "error",
40 | "no-unused-expressions": "error",
41 | "no-useless-call": "error",
42 | "no-useless-concat": "error",
43 | "no-useless-return": "error",
44 | "no-void": "error",
45 | "no-warning-comments": "error",
46 | "radix": "error",
47 | "no-shadow": "error",
48 | "no-use-before-define": ["error", { "functions": false }],
49 | "arrow-spacing": "error",
50 | "no-confusing-arrow": "error",
51 | "no-duplicate-imports": "error",
52 | "no-var": "error",
53 |
54 | "jsdoc/check-access": 1,
55 | "jsdoc/check-alignment": 1, // Recommended
56 | "jsdoc/check-examples": 1,
57 | "jsdoc/check-indentation": 1,
58 | "jsdoc/check-param-names": 1, // Recommended
59 | "jsdoc/check-syntax": 1,
60 | "jsdoc/check-tag-names": 1, // Recommended
61 | "jsdoc/check-types": 1, // Recommended
62 | "jsdoc/implements-on-classes": 1, // Recommended
63 | "jsdoc/match-description": 1,
64 | "jsdoc/no-undefined-types": 0, // Recommended
65 | "jsdoc/require-description": 1,
66 | "jsdoc/require-hyphen-before-param-description": 1,
67 | "jsdoc/require-jsdoc": ["error", {
68 | "require": {
69 | "ArrowFunctionExpression": false,
70 | "ClassDeclaration": true,
71 | "ClassExpression": true,
72 | "FunctionDeclaration": true,
73 | "FunctionExpression": true,
74 | "MethodDefinition": true
75 | }
76 | }],
77 | "jsdoc/require-param": 1, // Recommended
78 | "jsdoc/require-param-name": 1, // Recommended
79 | "jsdoc/require-param-type": 1, // Recommended
80 | "jsdoc/require-returns": 1, // Recommended
81 | "jsdoc/require-returns-check": 1, // Recommended
82 | "jsdoc/require-returns-type": 1, // Recommended
83 | "jsdoc/valid-types": 1 // Recommended
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/.github/workflows/docs-latest.yml:
--------------------------------------------------------------------------------
1 | name: Documentation (latest)
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/setup-node@v4
11 | - uses: actions/checkout@v4
12 | - run: npm install
13 | - run: npm run docs
14 | - name: get-npm-version
15 | id: package-version
16 | uses: martinbeentjes/npm-get-version-action@master
17 | - uses: peaceiris/actions-gh-pages@v4
18 | with:
19 | github_token: ${{ secrets.GITHUB_TOKEN }}
20 | publish_dir: docs/@openeo/js-client/${{ steps.package-version.outputs.current-version}}
21 | destination_dir: latest
22 | keep_files: true
23 | user_name: 'openEO CI'
24 | user_email: openeo.ci@uni-muenster.de
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Documentation (versioned)
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | deploy:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/setup-node@v4
10 | - uses: actions/checkout@v4
11 | - run: npm install
12 | - run: npm run docs
13 | - uses: peaceiris/actions-gh-pages@v4
14 | with:
15 | github_token: ${{ secrets.GITHUB_TOKEN }}
16 | publish_dir: docs/@openeo/js-client
17 | keep_files: true
18 | user_name: 'openEO CI'
19 | user_email: openeo.ci@uni-muenster.de
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on: [push, pull_request]
3 | jobs:
4 | checks:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/setup-node@v4
8 | - uses: actions/checkout@v4
9 | - run: npm install
10 | - run: npm run lint
11 | - run: npm run build
12 | - run: npm run docs
13 | tests:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/setup-node@v4
17 | - uses: actions/checkout@v4
18 | - run: npm install
19 | - run: npm run test
20 | - run: npm run test_node
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # NPM
2 | node_modules/
3 | /package-lock.json
4 |
5 | # Build related
6 | /report.html
7 | /openeo.js
8 | /openeo.min.js
9 | /openeo.node.js
10 | /openeo.node.min.js
11 |
12 | # Reports
13 | coverage/
14 | docs/
15 |
16 | # Temporary files from the earthengine tests
17 | /lorem.txt
18 | /randomnumber.txt
19 | /downloaded_file.txt
20 |
--------------------------------------------------------------------------------
/.jsdoc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["plugins/markdown"],
3 | "recurseDepth": 10,
4 | "source": {
5 | "include": ["src/"],
6 | "includePattern": ".+\\.js(doc|x)?$",
7 | "excludePattern": "(^|\\/|\\\\)_"
8 | },
9 | "sourceType": "module",
10 | "tags": {
11 | "allowUnknownTags": true,
12 | "dictionaries": ["jsdoc","closure"]
13 | },
14 | "templates": {
15 | "cleverLinks": false,
16 | "monospaceLinks": false
17 | }
18 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [2.8.0] - 2025-04-25
11 |
12 | ### Added
13 |
14 | - New property `id` added to `FederationBackend` objects returned by `listFederation` in `Capabilities`
15 | - New functions `getFederationBackend` and `getFederationBackends` in `Capabilities`
16 |
17 | ### Fixed
18 |
19 | - Updated/fixed Typescript declaration
20 |
21 | ## [2.7.0] - 2025-01-04
22 |
23 | ### Added
24 |
25 | - New function `getMissingBackends` for `Logs`
26 | - New property `federation:backends` added to the array returned by `validateProcess`
27 | - New property `axios` in the `Environment`
28 | - New functions in `Connection` for paginating through lists:
29 | - `paginateProcesses`
30 | - `paginateCollections`
31 | - `paginateJobs`
32 | - `paginateFiles`
33 | - `paginateUserProcesses`
34 | - `paginateServices`
35 |
36 | ### Changed
37 |
38 | - The `listCollectionItems` function in `Connection` was completely rewritten.
39 | - The client may add a self link to the links in responses
40 |
41 | ## [2.6.0] - 2024-07-11
42 |
43 | ### Added
44 |
45 | - Support to retrieve conformance classes
46 | - Added `Connection.makeLinksAbsolute`
47 | - Added `AuthProvider.getDisplayName`
48 | - Hook to migrate capabilities
49 | - Support for `usage`, `log_level`, `links` in batch jobs and services
50 | - Support for `level` parameter for log requests in batch jobs and services
51 | - Support passing additional parameters to synchronous jobs.
52 |
53 | ### Changed
54 |
55 | - Updated axios from 0.x to 1.x
56 |
57 | ### Removed
58 |
59 | - Dependency for `node-abort-controller` not required any longer in nodeJS
60 |
61 | ### Fixed
62 |
63 | - Make the feature / endpoint mapping more robust
64 | - Add missing endpoints to feature / endpoint mapping
65 | - Fix AbortController handling
66 | - Clarify that the `log_level` parameter for jobs, services and sync. processing can be provided via the `additional` parameter
67 |
68 | ## [2.5.1] - 2022-04-08
69 |
70 | ### Fixed
71 |
72 | - Job status missing from batch job metadata
73 |
74 | ## [2.5.0] - 2022-04-07
75 |
76 | ### Changed
77 |
78 | - `listJobs`, `listServices` and `listUserProcesses` allow to update existing processes.
79 | - `listFiles`, `listJobs`, `listServices` and `listUserProcesses` return a `ResponseArray` instead of a plain `Array`.
80 | The ResonseArray now has the properties `links` and `federation:missing` from the responses.
81 | This is usually a non-breaking change, but if you mistakenly use `Object` functions (such as `Object.keys`) on an array, you'll get the additional properties, too.
82 |
83 | ### Fixed
84 |
85 | - Fine-tuned some Typescript declarations.
86 |
87 | ## [2.4.1] - 2022-01-13
88 |
89 | ### Fixed
90 |
91 | - Fixed regression for OIDC scopes introduced in 2.4.0
92 |
93 | ## [2.4.0] - 2022-01-10
94 |
95 | ### Added
96 |
97 | - OpenID Connect: Support requesting a refresh token easily via the `requestRefreshToken` parameter.
98 |
99 | ### Changed
100 |
101 | - The `namespace` parameter in `listProcesses` and `describeProcess` parses URLs to extract the namespace (experimental).
102 |
103 | ### Fixed
104 |
105 | - Fixed several type annotations in the documentation and the TypeScript declaration file.
106 |
107 | ## [2.3.1] - 2021-12-10
108 |
109 | ### Fixed
110 |
111 | - Process cache gets refreshed after the authentication details have changed (e.g. the user has logged in or out).
112 |
113 | ## [2.3.0] - 2021-12-10
114 |
115 | ### Added
116 |
117 | - New parameter `abortController` to allow cancellation of longer running requests (uploading files, sync. data processing).
118 |
119 | ### Changed
120 |
121 | - Rarely used `multihashes` dependency not included in the bundle by default, see Readme to include it in the browser. No change for node environments.
122 |
123 | ## [2.2.0] - 2021-08-20
124 |
125 | ### Added
126 |
127 | - New method `getUrl` to class `Connection` to get the main URL of the back-end, which the user provided (i.e. doesn't usually contain version part)
128 |
129 | ## [2.1.0] - 2021-08-18
130 |
131 | ### Added
132 |
133 | - Experimental support for process namespaces, see [API#348](https://github.com/Open-EO/openeo-api/pull/348).
134 |
135 | ### Changed
136 |
137 | - Internally, a new process registry is used to manage and cache processes.
138 | - `listProcesses` doesn't cache requests any longer.
139 |
140 | ### Fixed
141 |
142 | - Return a better error message if issues with reading batch job results occur in `Job.getResultsAsStac`
143 | - `getAll()` functions return only properties for values that are defined
144 |
145 | ## [2.0.1] - 2021-07-14
146 |
147 | ### Fixed
148 |
149 | - `Formula`: Formulas can reference more then just the first parameter by adding additional `$` at the beginning, e.g. `$$0` to access the first element of an array in the second parameter.
150 |
151 | ## [2.0.0] - 2021-07-06
152 |
153 | ### Added
154 |
155 | - Added events to `Connection` to populate changes for Auth Tokens (`tokenChanged`) and Auth Providers (`authProviderChanged`).
156 | - Added new methods to `Connection` for working with events: `on`, `off` and `emit`.
157 |
158 | ### Changed
159 |
160 | - OpenID Connect authentication has been rewritten.
161 | - Default grant type for OpenID Connect is "AuthCode w/ PKCE" instead of "Implicit".
162 | - Support for OpenID Connect session renewal via refresh tokens.
163 | - Updated STAC support to STAC v1.0.0.
164 |
165 | ### Removed
166 |
167 | - `OidcProvider`: Methods `getGrant`, `getScopes`, `getIssuer` and `getUser` removed. Use the properties `grant`, `scopes`, `issuer` and `user` instead.
168 | - Removed deprecated method `getResultsAsItem` in favor of `getResultsAsStac`.
169 |
170 | ## [1.3.2] - 2021-05-27
171 |
172 | ### Fixed
173 |
174 | - Fixed error handling for HTTP requests
175 |
176 | ## [1.3.1] - 2021-04-29
177 |
178 | ### Fixed
179 |
180 | - Invalid dependency version for @openeo/js-commons
181 |
182 | ## [1.3.0] - 2021-04-29
183 |
184 | ### Added
185 |
186 | - Custom process specifications can be added to the process builder after first initialization.
187 | - The process builder supports implicitly wrapping:
188 | - an array returned from a callback using the `array_create` process.
189 | - non-objects (e.g. numbers) returned from a callback using the `constant` process.
190 | - Support for "Default OpenID Connect Clients" (detecting default client IDs).
191 |
192 | ### Fixed
193 |
194 | - Arrow functions can be used as callbacks in process graph building.
195 | - Fixed nullable return types in TS declaraion
196 | - Fixed other minor issues in TS declaration
197 |
198 | ## [1.2.0] - 2021-03-11
199 |
200 | ### Added
201 |
202 | - Added new method `listCollectionItems` for searching and retrieving Items from `GET /collections/{collectionId}/items`.
203 |
204 | ### Changed
205 |
206 | - Methods returning STAC (e.g. `listCollections`, `describeCollection`, `getResultAsStac`, `listCollectionItems`) always return the data compliant to the latest STAC version (currently 1.0.0-rc.1).
207 |
208 | ## [1.1.0] - 2021-02-18
209 |
210 | ### Added
211 |
212 | - Added new method `getResultsAsStac` for Jobs, which includes support for STAC Collections returned by the API for batch job results.
213 |
214 | ### Deprecated
215 |
216 | - Deprecated method `getResultsAsItem` in favor of `getResultsAsStac`.
217 |
218 | ### Fixed
219 |
220 | - TypeScript declaration for Links has been fixed
221 | - Updated dependencies
222 |
223 | ## [1.0.3] - 2021-02-02
224 |
225 | ### Fixed
226 |
227 | - Updated axios dependency to fix a vulnerability.
228 | - Updated oidc-client dependency to support the OpenID Connect flow *AuthCode with PKCE*. Default is still the Implicit Flow.
229 |
230 | ## [1.0.2] - 2020-12-02
231 |
232 | ### Changed
233 | - Make client more flexible to allow setting the client to use OIDC Authentication Code Flow with PKCE instead of Implicit Flow in the future
234 |
235 | ### Fixed
236 | - Client doesn't throw errors on back-ends with version numbers >= 1.0.1
237 |
238 | ## [1.0.1] - 2020-11-19
239 |
240 | ### Fixed
241 | - Don't set unnecessary `withCredentials` option on HTTP requests for better CORS support. [openeo-api#41](https://github.com/Open-EO/openeo-api/issues/41)
242 |
243 | ## [1.0.0] - 2020-11-17
244 |
245 | ### Fixed
246 | - Throw better error message in case openeo-identifier can't be retrieved. [#37](https://github.com/Open-EO/openeo-js-client/issues/37)
247 |
248 | ## Prior releases
249 |
250 | All prior releases have been documented in the [GitHub Releases](https://github.com/Open-EO/openeo-js-client/releases).
251 |
252 | [Unreleased]: https://github.com/Open-EO/openeo-js-client/compare/v2.8.0...HEAD
253 | [2.8.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.7.0...v2.8.0
254 | [2.7.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.6.0...v2.7.0
255 | [2.6.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.5.1...v2.6.0
256 | [2.5.1]: https://github.com/Open-EO/openeo-js-client/compare/v2.5.0...v2.5.1
257 | [2.5.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.4.1...v2.5.0
258 | [2.4.1]: https://github.com/Open-EO/openeo-js-client/compare/v2.4.0...v2.4.1
259 | [2.4.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.3.1...v2.4.0
260 | [2.3.1]: https://github.com/Open-EO/openeo-js-client/compare/v2.3.0...v2.3.1
261 | [2.3.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.2.0...v2.3.0
262 | [2.2.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.1.0...v2.2.0
263 | [2.1.0]: https://github.com/Open-EO/openeo-js-client/compare/v2.0.1...v2.1.0
264 | [2.0.1]: https://github.com/Open-EO/openeo-js-client/compare/v2.0.0...v2.0.1
265 | [2.0.0]: https://github.com/Open-EO/openeo-js-client/compare/v1.3.2...v2.0.0
266 | [1.3.2]: https://github.com/Open-EO/openeo-js-client/compare/v1.3.1...v1.3.2
267 | [1.3.1]: https://github.com/Open-EO/openeo-js-client/compare/v1.3.0...v1.3.1
268 | [1.3.0]: https://github.com/Open-EO/openeo-js-client/compare/v1.2.0...v1.3.0
269 | [1.2.0]: https://github.com/Open-EO/openeo-js-client/compare/v1.1.0...v1.2.0
270 | [1.1.0]: https://github.com/Open-EO/openeo-js-client/compare/v1.0.3...v1.1.0
271 | [1.0.3]: https://github.com/Open-EO/openeo-js-client/compare/v1.0.2...v1.0.3
272 | [1.0.2]: https://github.com/Open-EO/openeo-js-client/compare/v1.0.1...v1.0.2
273 | [1.0.1]: https://github.com/Open-EO/openeo-js-client/compare/v1.0.0...v1.0.1
274 | [1.0.0]: https://github.com/Open-EO/openeo-js-client/compare/v1.0.0-rc.5...v1.0.0
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # openeo-js-client
2 |
3 | JavaScript/TypeScript client for the openEO API.
4 |
5 | * [Documentation](https://open-eo.github.io/openeo-js-client/latest/).
6 |
7 | The version of this client is **2.8.0** and supports **openEO API versions 1.x.x**.
8 | Legacy versions are available as releases.
9 | See the [CHANGELOG](CHANGELOG.md) for recent changes.
10 |
11 | ## Usage
12 |
13 | This library can run in node.js or any recent browser supporting ECMAScript 2017. This excludes Internet Explorer, but includes Edge >= 15.
14 |
15 | An *experimental* Typescript declaration file is available so that you can use the library also in your TypeScript projects.
16 |
17 | ### Browser
18 |
19 | To use it in a browser environment simply add the following code to your HTML file:
20 | ```html
21 |
22 |
23 |
24 |
25 | ```
26 |
27 | ### NodeJS
28 |
29 | To install it in a NodeJS environment run:
30 | `npm install @openeo/js-client`
31 |
32 | Afterwards, you can import the package:
33 | `const { OpenEO } = require('@openeo/js-client');`
34 |
35 | ### TypeScript
36 |
37 | Warning: The TypeScript integration is still **experimental**! Please help us improve it by opening issues or pull requests.
38 |
39 | To install it in a TypeScript environment run:
40 | `npm install @openeo/js-client`
41 |
42 | Afterwards, you can import the package:
43 | `import { OpenEO } from '@openeo/js-client';`
44 |
45 | ### Examples
46 |
47 | In the browser:
48 | * [Basic Discovery (promises)](examples/web/discovery.html)
49 | * [Run sync. job (async/await)](examples/web/workflow.html)
50 |
51 | In Node.js:
52 | * [Basic Discovery (promises)](examples/node/discovery.js)
53 |
54 | In Typescript:
55 | * [Basic Discovery (promises)](examples/typescript/discovery.ts)
56 |
57 | More information can be found in the [documentation](https://open-eo.github.io/openeo-js-client/latest/).
58 |
59 | ## Development
60 |
61 | 
62 |
63 | Always make sure to adapt changes in the *.js files to the openeo.d.ts file.
64 | If changes are larger you may want to run `npm run tsd` and regenerate the declaration file and cherry-pick your changes from there.
65 |
66 | Generate a build: `npm run build` (generates `openeo.js` and `openeo.min.js`)
67 |
68 | Generate the documentation to the `docs/` folder: `npm run docs`
69 |
70 | Check against the coding guidelines: `npm run lint`
71 |
72 | Run tests:
73 |
74 | * `npm test` (all tests)
75 | * `npm test browser` (browser tests)
76 | * `npm test node` (node tests)
77 | * `npm test builder` (tests only the process builder)
78 | * `npm test earthengine` (full test suite using the Google Earth Engine back-end as server)
79 |
80 | # Contributions
81 |
82 | The authors acknowledge the financial support for the development of this package during the H2020 project "openEO" (Oct 2017 to Sept 2020) by the European Union, funded by call EO-2-2017: EO Big Data Shift, under grant number 776242. We also acknowledge the financial support received from ESA for the project "openEO Platform" (Sept 2020 to Sept 2023).
83 |
84 | This package received major contributions from the following organizations:
85 |
86 | [ ](https://www.uni-muenster.de/)
87 | [ ](https://www.solenix.ch)
88 | [ ](https://www.sinergise.com)
89 |
90 | ## Interactive Web Editor
91 |
92 | There is an interactive web-based editor for coding using the openEO API,
93 | which is based on the JavaScript client.
94 | See [https://github.com/Open-EO/openeo-web-editor](https://github.com/Open-EO/openeo-web-editor) for more details.
95 |
--------------------------------------------------------------------------------
/examples/node/discovery.js:
--------------------------------------------------------------------------------
1 | // Import the JS client
2 | const { OpenEO } = require('@openeo/js-client');
3 |
4 | const url = "https://earthengine.openeo.org"; // Insert the openEO server URL here
5 | let connection = null;
6 |
7 | console.log('URL: ' + url);
8 | console.log('Client Version: ' + OpenEO.clientVersion());
9 |
10 | OpenEO.connect(url)
11 | .then(c => {
12 | connection = c;
13 | return connection.capabilities();
14 | })
15 | .then(capabilities => {
16 | console.log('Server Version: ' + capabilities.apiVersion());
17 | return connection.listCollections();
18 | })
19 | .then(collections => {
20 | console.log('Number of supported collections: ' + collections.collections.length);
21 | return connection.listProcesses();
22 | })
23 | .then(processes => {
24 | console.log('Number of supported processes: ' + processes.processes.length);
25 | })
26 | .catch(err => console.error(err.message));
27 |
--------------------------------------------------------------------------------
/examples/node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openeo/js-client-example-node",
3 | "version": "0.1.0",
4 | "author": "openEO Consortium",
5 | "contributors": [
6 | {
7 | "name": "Christoph Friedrich"
8 | },
9 | {
10 | "name": "Matthias Mohr"
11 | }
12 | ],
13 | "description": "Example showcasing the usage of the openEO JavaScript client in Node.js",
14 | "license": "Apache-2.0",
15 | "homepage": "http://openeo.org",
16 | "bugs": {
17 | "url": "https://github.com/Open-EO/openeo-js-client/issues"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/Open-EO/openeo-js-client.git"
22 | },
23 | "main": "discovery.js",
24 | "dependencies": {
25 | "@openeo/js-client": "^1.0.0"
26 | },
27 | "scripts": {
28 | "start": "node discovery.js"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/oidc/openid-connect-popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | openEO JS client - OpenID Connect example
6 |
7 |
8 |
9 |
10 |
11 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/examples/oidc/openid-connect-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | openEO JS client - OpenID Connect example
6 |
7 |
8 |
9 |
10 |
11 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/examples/typescript/.gitignore:
--------------------------------------------------------------------------------
1 | discovery.js
2 | package-lock.json
3 |
--------------------------------------------------------------------------------
/examples/typescript/discovery.ts:
--------------------------------------------------------------------------------
1 | // import the JS client main part (OpenEO) for the actual functionality
2 | // and some classes (Connection, Capabilities) and types (Collections, Processes) for TypeScript stuff
3 | import { OpenEO, Connection, Capabilities, Collections, Processes } from '@openeo/js-client';
4 |
5 | let url: string = "https://earthengine.openeo.org"; // Insert the openEO server URL here
6 | let connection: Connection = null; // Reserve a variable for the connection and specify its type
7 |
8 | console.log('URL: ' + url);
9 | console.log('Client Version: ' + OpenEO.clientVersion());
10 |
11 | OpenEO.connect(url)
12 | .then((c: Connection): Capabilities => { // specify parameter type and return type
13 | connection = c;
14 | return connection.capabilities();
15 | })
16 | .then((capabilities: Capabilities): Promise => { // as before, note the `Promise<>` generic
17 | console.log('Server Version: ' + capabilities.apiVersion());
18 | return connection.listCollections();
19 | })
20 | .then((collections: Collections): Promise => { // note that `Promise` has become `Collections`
21 | console.log('Number of supported collections: ' + collections.collections.length);
22 | return connection.listProcesses();
23 | })
24 | .then((processes: Processes): void => { // final callback in chain doesn't return anything
25 | console.log('Number of supported processes: ' + processes.processes.length);
26 | return;
27 | })
28 | .catch((err: Error) => console.error(err.message));
29 |
--------------------------------------------------------------------------------
/examples/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openeo/js-client-example-typescript",
3 | "version": "0.1.0",
4 | "author": "openEO Consortium",
5 | "contributors": [
6 | {
7 | "name": "Christoph Friedrich"
8 | },
9 | {
10 | "name": "Matthias Mohr"
11 | }
12 | ],
13 | "description": "Example showcasing the usage of the openEO JavaScript client in Typescript",
14 | "license": "Apache-2.0",
15 | "homepage": "http://openeo.org",
16 | "bugs": {
17 | "url": "https://github.com/Open-EO/openeo-js-client/issues"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/Open-EO/openeo-js-client.git"
22 | },
23 | "main": "discovery.ts",
24 | "dependencies": {
25 | "@openeo/js-client": "^1.0.0",
26 | "typescript": "^4.0.5"
27 | },
28 | "scripts": {
29 | "build": "tsc discovery.ts",
30 | "start": "node discovery.js"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/web/discovery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | openEO JS client - Discovery example
6 |
7 |
8 |
9 |
10 |
38 |
39 |
40 |
41 | Server information
42 | URL:
43 | Versions
44 |
45 | Client Version: Loading...
46 | Server Version: Loading...
47 |
48 | EO Data Discovery
49 | Number of supported collections: Loading...
50 | Process Discovery
51 | Number of supported processes: Loading...
52 |
53 |
54 |
--------------------------------------------------------------------------------
/examples/web/workflow.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | openEO JS client - Workflow example
6 |
7 |
8 |
9 |
10 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | // Indicates whether the coverage information should be collected while executing the test
4 | collectCoverage: true,
5 |
6 | globals: {
7 | 'oidc-client': require('oidc-client')
8 | },
9 |
10 | moduleNameMapper: {
11 | "^@openeo/js-environment(.*)$": "/src$1",
12 | "axios": "/node_modules/axios/dist/node/axios.cjs"
13 | },
14 |
15 | // The directory where Jest should output its coverage files
16 | coverageDirectory: "coverage",
17 |
18 | // Make calling deprecated APIs throw helpful error messages
19 | errorOnDeprecated: true,
20 |
21 | // Use this configuration option to add custom reporters to Jest
22 | "reporters": [
23 | "default",
24 | ["./node_modules/jest-html-reporter", {
25 | "pageTitle": "Test Report for openeo-js-client",
26 | "outputPath": "./coverage/test-report.html"
27 | }]
28 | ]
29 |
30 | };
31 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["./src/"],
3 | "compilerOptions": {
4 | "checkJs": true,
5 | "strict": true,
6 | "target": "es2017"
7 | }
8 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@openeo/js-client",
3 | "version": "2.8.0",
4 | "author": "openEO Consortium",
5 | "contributors": [
6 | {
7 | "name": "Matthias Mohr"
8 | },
9 | {
10 | "name": "Christoph Friedrich"
11 | },
12 | {
13 | "name": "Joep Neijt"
14 | }
15 | ],
16 | "description": "JavaScript client for the openEO API.",
17 | "license": "Apache-2.0",
18 | "homepage": "http://openeo.org",
19 | "bugs": {
20 | "url": "https://github.com/Open-EO/openeo-js-client/issues"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/Open-EO/openeo-js-client.git"
25 | },
26 | "funding": {
27 | "type": "github",
28 | "url": "https://github.com/sponsors/m-mohr"
29 | },
30 | "main": "src/openeo.js",
31 | "types": "openeo.d.ts",
32 | "files": [
33 | "openeo.js",
34 | "openeo.d.ts",
35 | "openeo.min.js",
36 | "src/*"
37 | ],
38 | "devDependencies": {
39 | "@babel/core": "^7.12.9",
40 | "@babel/preset-env": "^7.12.7",
41 | "babel-loader": "^8.2.2",
42 | "eslint": "^7.14.0",
43 | "eslint-plugin-jsdoc": "^30.7.8",
44 | "jest": "^27.4.4",
45 | "jest-html-reporter": "^3.3.0",
46 | "jsdoc": "^4.0.0",
47 | "typescript": "^5.0.0",
48 | "unminified-webpack-plugin": "^3.0.0",
49 | "wait-for-expect": "^1.3.0",
50 | "webpack": "^5.65.0",
51 | "webpack-bundle-analyzer": "^4.0.0",
52 | "webpack-cli": "^4.9.1"
53 | },
54 | "dependencies": {
55 | "@openeo/js-commons": "^1.5.0",
56 | "@radiantearth/stac-migrate": "^1.0.0",
57 | "axios": "^1.0.0",
58 | "oidc-client": "^1.11.5"
59 | },
60 | "scripts": {
61 | "docs": "jsdoc -r -d docs/ -P package.json -R README.md -c .jsdoc",
62 | "build": "npm run lint && webpack",
63 | "tsd": "tsc --project tsconfig.json",
64 | "lint": "eslint src/",
65 | "test": " jest --env=jsdom --runInBand",
66 | "test_node": " jest --env=node --runInBand"
67 | },
68 | "browserslist": [
69 | "> 1%",
70 | "not ie > 0"
71 | ]
72 | }
73 |
--------------------------------------------------------------------------------
/src/authprovider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The base class for authentication providers such as Basic and OpenID Connect.
3 | *
4 | * @abstract
5 | */
6 | class AuthProvider {
7 |
8 | /**
9 | * Creates a new OidcProvider instance to authenticate using OpenID Connect.
10 | *
11 | * @param {string} type - The type of the authentication procedure as specified by the API, e.g. `oidc` or `basic`.
12 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
13 | * @param {AuthProviderMeta} options - Options
14 | */
15 | constructor(type, connection, options) {
16 | this.id = options.id || null;
17 | this.title = options.title || "";
18 | this.description = options.description || "";
19 | this.type = type;
20 | /**
21 | * @protected
22 | * @type {Connection}
23 | */
24 | this.connection = connection;
25 | this.token = null;
26 | }
27 |
28 | /**
29 | * Get an identifier for the auth provider (combination of the type + provider identifier).
30 | *
31 | * @returns {string}
32 | */
33 | getId() {
34 | let id = this.getType();
35 | if (this.getProviderId().length > 0) {
36 | id += '.' + this.getProviderId();
37 | }
38 | return id;
39 | }
40 |
41 | /**
42 | * Returns a display name for the authenticated user.
43 | *
44 | * @returns {string?} Name of the user or `null`
45 | */
46 | getDisplayName() {
47 | return null;
48 | }
49 |
50 | /**
51 | * Returns the type of the authentication procedure as specified by the API, e.g. `oidc` or `basic`.
52 | *
53 | * @returns {string}
54 | */
55 | getType() {
56 | return this.type;
57 | }
58 |
59 | /**
60 | * Returns the provider identifier, may not be available for all authentication methods.
61 | *
62 | * @returns {string}
63 | */
64 | getProviderId() {
65 | return typeof this.id === 'string' ? this.id : "";
66 | }
67 |
68 | /**
69 | * Returns the human-readable title for the authentication method / provider.
70 | *
71 | * @returns {string}
72 | */
73 | getTitle() {
74 | return this.title;
75 | }
76 |
77 | /**
78 | * Returns the human-readable description for the authentication method / provider.
79 | *
80 | * @returns {string}
81 | */
82 | getDescription() {
83 | return this.description;
84 | }
85 |
86 | /**
87 | * Returns the access token that is used as Bearer Token in API requests.
88 | *
89 | * Returns `null` if no access token has been set yet (i.e. not authenticated any longer).
90 | *
91 | * @returns {string | null}
92 | */
93 | getToken() {
94 | if (typeof this.token === 'string') {
95 | return this.getType() + "/" + this.getProviderId() + "/" + this.token;
96 | }
97 | else {
98 | return null;
99 | }
100 | }
101 |
102 | /**
103 | * Sets the access token that is used as Bearer Token in API requests.
104 | *
105 | * Set to `null` to remove the access token.
106 | *
107 | * This also manages which auth provider is set for the connection.
108 | *
109 | * @param {?string} token
110 | */
111 | setToken(token) {
112 | this.token = token;
113 | this.connection.emit('tokenChanged', token);
114 | if (this.token !== null) {
115 | this.connection.setAuthProvider(this);
116 | }
117 | else {
118 | this.connection.setAuthProvider(null);
119 | }
120 | }
121 |
122 | /**
123 | * Abstract method that extending classes implement the login process with.
124 | *
125 | * @async
126 | * @param {...*} args
127 | * @throws {Error}
128 | */
129 | async login(...args) {
130 | throw new Error("Not implemented.", args);
131 | }
132 |
133 | /**
134 | * Logout from the established session.
135 | *
136 | * This is experimental and just removes the token for now.
137 | * May need to be overridden by sub-classes.
138 | *
139 | * @async
140 | */
141 | async logout() {
142 | this.setToken(null);
143 | }
144 |
145 | }
146 |
147 | module.exports = AuthProvider;
148 |
--------------------------------------------------------------------------------
/src/baseentity.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The base class for entities such as Job, Process Graph, Service etc.
3 | *
4 | * @abstract
5 | */
6 | class BaseEntity {
7 |
8 | /**
9 | * Creates an instance of this object.
10 | *
11 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
12 | * @param {Array.>} properties - A mapping from the API property names to the JS client property names (usually to convert between snake_case and camelCase), e.g. `["id", "title", ["process_graph", "processGraph"]]`
13 | */
14 | constructor(connection, properties = []) {
15 | /**
16 | * @protected
17 | * @type {Connection}
18 | */
19 | this.connection = connection;
20 | /**
21 | * @protected
22 | * @type {object.}
23 | */
24 | this.apiToClientNames = {};
25 | /**
26 | * @protected
27 | * @type {object.}
28 | */
29 | this.clientToApiNames = {};
30 | /**
31 | * @protected
32 | * @type {number}
33 | */
34 | this.lastRefreshTime = 0;
35 | /**
36 | * Additional (non-standardized) properties received from the API.
37 | *
38 | * @protected
39 | * @type {object.}
40 | */
41 | this.extra = {};
42 |
43 | for(let i in properties) {
44 | let backend, client;
45 | if (Array.isArray(properties[i])) {
46 | backend = properties[i][0];
47 | client = properties[i][1];
48 | }
49 | else {
50 | backend = properties[i];
51 | client = properties[i];
52 | }
53 | this.apiToClientNames[backend] = client;
54 | this.clientToApiNames[client] = backend;
55 | }
56 | }
57 |
58 | /**
59 | * Returns a JSON serializable representation of the data that is API compliant.
60 | *
61 | * @returns {object.}
62 | */
63 | toJSON() {
64 | let obj = {};
65 | for(let key in this.clientToApiNames) {
66 | let apiKey = this.clientToApiNames[key];
67 | if (typeof this[key] !== 'undefined') {
68 | obj[apiKey] = this[key];
69 | }
70 | }
71 | return Object.assign(obj, this.extra);
72 | }
73 |
74 | /**
75 | * Converts the data from an API response into data suitable for our JS client models.
76 | *
77 | * @param {object.} metadata - JSON object originating from an API response.
78 | * @returns {BaseEntity} Returns the object itself.
79 | */
80 | setAll(metadata) {
81 | for(let name in metadata) {
82 | if (typeof this.apiToClientNames[name] === 'undefined') {
83 | this.extra[name] = metadata[name];
84 | }
85 | else {
86 | this[this.apiToClientNames[name]] = metadata[name];
87 | }
88 | }
89 | this.lastRefreshTime = Date.now();
90 | return this;
91 | }
92 |
93 | /**
94 | * Returns the age of the data in seconds.
95 | *
96 | * @returns {number} Age of the data in seconds as integer.
97 | */
98 | getDataAge() {
99 | return (Date.now() - this.lastRefreshTime) / 1000;
100 | }
101 |
102 | /**
103 | * Returns all data in the model.
104 | *
105 | * @returns {object.}
106 | */
107 | getAll() {
108 | let obj = {};
109 | for(let backend in this.apiToClientNames) {
110 | let client = this.apiToClientNames[backend];
111 | if (typeof this[client] !== 'undefined') {
112 | obj[client] = this[client];
113 | }
114 | }
115 | return Object.assign(obj, this.extra);
116 | }
117 |
118 | /**
119 | * Get a value from the additional data that is not part of the core model, i.e. from proprietary extensions.
120 | *
121 | * @param {string} name - Name of the property.
122 | * @returns {*} The value, which could be of any type.
123 | */
124 | get(name) {
125 | return typeof this.extra[name] !== 'undefined' ? this.extra[name] : null;
126 | }
127 |
128 | /**
129 | * Converts the object to a valid objects for API requests.
130 | *
131 | * @param {object.} parameters
132 | * @returns {object.}
133 | * @protected
134 | */
135 | _convertToRequest(parameters) {
136 | let request = {};
137 | for(let key in parameters) {
138 | if (typeof this.clientToApiNames[key] === 'undefined') {
139 | request[key] = parameters[key];
140 | }
141 | else {
142 | request[this.clientToApiNames[key]] = parameters[key];
143 | }
144 | }
145 | return request;
146 | }
147 |
148 | /**
149 | * Checks whether a features is supported by the API.
150 | *
151 | * @param {string} feature
152 | * @returns {boolean}
153 | * @protected
154 | * @see Capabilities#hasFeature
155 | */
156 | _supports(feature) {
157 | return this.connection.capabilities().hasFeature(feature);
158 | }
159 |
160 | }
161 |
162 | module.exports = BaseEntity;
163 |
--------------------------------------------------------------------------------
/src/basicprovider.js:
--------------------------------------------------------------------------------
1 | const Environment = require('./env');
2 | const Utils = require('@openeo/js-commons/src/utils');
3 | const AuthProvider = require('./authprovider');
4 |
5 | /**
6 | * The Authentication Provider for HTTP Basic.
7 | *
8 | * @augments AuthProvider
9 | */
10 | class BasicProvider extends AuthProvider {
11 |
12 | /**
13 | * Creates a new BasicProvider instance to authenticate using HTTP Basic.
14 | *
15 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
16 | */
17 | constructor(connection) {
18 | super("basic", connection, {
19 | id: null,
20 | title: "HTTP Basic",
21 | description: "Login with username and password using the method HTTP Basic."
22 | });
23 | this.username = null;
24 | }
25 |
26 | /**
27 | * Authenticate with HTTP Basic.
28 | *
29 | * @async
30 | * @param {string} username
31 | * @param {string} password
32 | * @returns {Promise}
33 | * @throws {Error}
34 | */
35 | async login(username, password) {
36 | let response = await this.connection._send({
37 | method: 'get',
38 | responseType: 'json',
39 | url: '/credentials/basic',
40 | headers: {'Authorization': 'Basic ' + Environment.base64encode(username + ':' + password)}
41 | });
42 | if (!Utils.isObject(response.data) || typeof response.data.access_token !== 'string') {
43 | throw new Error("No access_token returned.");
44 | }
45 | this.username = username;
46 | this.setToken(response.data.access_token);
47 | }
48 |
49 | /**
50 | * Returns a display name for the authenticated user.
51 | *
52 | * @returns {string?} Name of the user or `null`
53 | */
54 | getDisplayName() {
55 | return this.username;
56 | }
57 |
58 | /**
59 | * Logout from the established session.
60 | *
61 | * @async
62 | */
63 | async logout() {
64 | this.username = null;
65 | await super.logout();
66 | }
67 |
68 | }
69 |
70 | module.exports = BasicProvider;
--------------------------------------------------------------------------------
/src/browser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Platform dependant utilities for the openEO JS Client.
3 | *
4 | * Browser implementation, don't use in other environments.
5 | *
6 | * @hideconstructor
7 | */
8 | class Environment {
9 |
10 | /**
11 | * Returns the name of the Environment, here `Browser`.
12 | *
13 | * @returns {string}
14 | * @static
15 | */
16 | static getName() {
17 | return 'Browser';
18 | }
19 |
20 | /**
21 | * Returns the current URL of the browser window.
22 | *
23 | * @returns {string}
24 | * @static
25 | */
26 | static getUrl() {
27 | return window.location.toString();
28 | }
29 |
30 | /**
31 | * Sets the URL.
32 | *
33 | * Not supported in Browsers and only throws an Error!
34 | *
35 | * @param {string} uri
36 | * @static
37 | */
38 | static setUrl(uri) { // eslint-disable-line no-unused-vars
39 | throw new Error("setUrl is not supported in a browser environment.");
40 | }
41 |
42 | /**
43 | * Handles errors from the API that are returned as Blobs.
44 | *
45 | * @ignore
46 | * @static
47 | * @param {Blob} error
48 | * @returns {Promise}
49 | */
50 | static handleErrorResponse(error) {
51 | return new Promise((resolve, reject) => {
52 | let fileReader = new FileReader();
53 | fileReader.onerror = event => {
54 | fileReader.abort();
55 | reject(event.target.error);
56 | };
57 | fileReader.onload = () => {
58 | // ArrayBuffer to String conversion is from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
59 | let res = fileReader.result instanceof ArrayBuffer ? String.fromCharCode.apply(null, new Uint16Array(fileReader.result)) : fileReader.result;
60 | let obj = typeof res === 'string' ? JSON.parse(res) : res;
61 | resolve(obj);
62 | };
63 | fileReader.readAsText(error.response.data);
64 | });
65 | }
66 |
67 | /**
68 | * Returns how binary responses from the servers are returned (`stream` or `blob`).
69 | *
70 | * @returns {string}
71 | * @static
72 | */
73 | static getResponseType() {
74 | return 'blob';
75 | }
76 |
77 | /**
78 | * Encodes a string into Base64 encoding.
79 | *
80 | * @static
81 | * @param {string} str - String to encode.
82 | * @returns {string} String encoded in Base64.
83 | */
84 | static base64encode(str) {
85 | // btoa is JS's ugly name for encodeBase64
86 | return btoa(str);
87 | }
88 |
89 | /**
90 | * Detect the file name for the given data source.
91 | *
92 | * @ignore
93 | * @static
94 | * @param {*} source - An object from a file upload form.
95 | * @returns {string}
96 | */
97 | static fileNameForUpload(source) {
98 | return source.name.split(/(\\|\/)/g).pop();
99 | }
100 |
101 | /**
102 | * Get the data from the source that should be uploaded.
103 | *
104 | * @ignore
105 | * @static
106 | * @param {*} source - An object from a file upload form.
107 | * @returns {*}
108 | */
109 | static dataForUpload(source) {
110 | return source;
111 | }
112 |
113 | /**
114 | * Downloads files to local storage and returns a list of file paths.
115 | *
116 | * Not supported in Browsers and only throws an Error!
117 | *
118 | * @static
119 | * @param {Connection} con
120 | * @param {Array.>} assets
121 | * @param {string} targetFolder
122 | * @throws {Error}
123 | */
124 | static async downloadResults(con, assets, targetFolder) { // eslint-disable-line no-unused-vars
125 | throw new Error("downloadResults is not supported in a browser environment.");
126 | }
127 |
128 | /**
129 | * Offers data to download in the browser.
130 | *
131 | * This method may fail with overly big data.
132 | *
133 | * @async
134 | * @static
135 | * @param {*} data - Data to download.
136 | * @param {string} filename - File name that is suggested to the user.
137 | * @returns {Promise}
138 | * @see https://github.com/kennethjiang/js-file-download/blob/master/file-download.js
139 | */
140 | static saveToFile(data, filename) {
141 | /* istanbul ignore next */
142 | return new Promise((resolve, reject) => {
143 | try {
144 | if (!(data instanceof Blob)) {
145 | data = new Blob([data], {type: 'application/octet-stream'});
146 | }
147 | let blobURL = window.URL.createObjectURL(data);
148 | let tempLink = document.createElement('a');
149 | tempLink.style.display = 'none';
150 | tempLink.href = blobURL;
151 | tempLink.setAttribute('download', filename || 'download');
152 | if (typeof tempLink.download === 'undefined') {
153 | tempLink.setAttribute('target', '_blank');
154 | }
155 | document.body.appendChild(tempLink);
156 | tempLink.click();
157 | document.body.removeChild(tempLink);
158 | window.URL.revokeObjectURL(blobURL);
159 | resolve();
160 | } catch (error) {
161 | console.error(error);
162 | reject(error);
163 | }
164 | });
165 | }
166 | }
167 |
168 | module.exports = Environment;
169 |
--------------------------------------------------------------------------------
/src/builder/formula.js:
--------------------------------------------------------------------------------
1 | const TapDigit = require("./tapdigit");
2 | const Parameter = require("./parameter");
3 | const BuilderNode = require('./node');
4 |
5 | /**
6 | * This converts a mathematical formula into a openEO process for you.
7 | *
8 | * Operators: - (subtract), + (add), / (divide), * (multiply), ^ (power)
9 | *
10 | * It supports all mathematical functions (i.e. expects a number and returns a number) the back-end implements, e.g. `sqrt(x)`.
11 | * For namespaced processes, use for example `process@namespace(x)` - EXPERIMENTAL!
12 | *
13 | * Only available if a builder is specified in the constructor:
14 | * You can refer to output from processes with a leading `#`, e.g. `#loadco1` if the node to refer to has the key `loadco1`.
15 | *
16 | * Only available if a parent node is set via `setNode()`:
17 | * Parameters can be accessed simply by name.
18 | * If the first parameter is a (labeled) array, the value for a specific index or label can be accessed by typing the numeric index or textual label with a `$` in front, for example `$B1` for the label `B1` or `$0` for the first element in the array. Numeric labels are not supported.
19 | * You can access subsequent parameters by adding additional `$` at the beginning, e.g. `$$0` to access the first element of an array in the second parameter, `$$$0` for the same in the third parameter etc.
20 | *
21 | * An example that computes an EVI (assuming the labels for the bands are `NIR`, `RED` and `BLUE`): `2.5 * ($NIR - $RED) / (1 + $NIR + 6 * $RED + (-7.5 * $BLUE))`
22 | */
23 | class Formula {
24 |
25 | /**
26 | * Creates a math formula object.
27 | *
28 | * @param {string} formula - A mathematical formula to parse.y
29 | */
30 | constructor(formula) {
31 | let parser = new TapDigit.Parser();
32 | /**
33 | * @type {object.}
34 | */
35 | this.tree = parser.parse(formula);
36 | /**
37 | * @type {Builder | null}
38 | */
39 | this.builder = null;
40 | }
41 |
42 | /**
43 | * The builder instance to use.
44 | *
45 | * @param {Builder} builder - The builder instance to add the formula to.
46 | */
47 | setBuilder(builder) {
48 | this.builder = builder;
49 | }
50 |
51 | /**
52 | * Generates the processes for the formula specified in the constructor.
53 | *
54 | * Returns the last node that computes the result.
55 | *
56 | * @param {boolean} setResultNode - Set the `result` flag to `true`.
57 | * @returns {BuilderNode}
58 | * @throws {Error}
59 | */
60 | generate(setResultNode = true) {
61 | let finalNode = this.parseTree(this.tree);
62 | if (!(finalNode instanceof BuilderNode)) {
63 | throw new Error('Invalid formula specified.');
64 | }
65 | // Set result node
66 | if (setResultNode) {
67 | finalNode.result = true;
68 | }
69 | return finalNode;
70 | }
71 |
72 | /**
73 | * Walks through the tree generated by the TapDigit parser and generates process nodes.
74 | *
75 | * @protected
76 | * @param {object.} tree
77 | * @returns {object.}
78 | * @throws {Error}
79 | */
80 | parseTree(tree) {
81 | let key = Object.keys(tree)[0]; // There's never more than one property so no loop required
82 | switch(key) {
83 | case 'Number':
84 | return parseFloat(tree.Number);
85 | case 'Identifier':
86 | return this.getRef(tree.Identifier);
87 | case 'Expression':
88 | return this.parseTree(tree.Expression);
89 | case 'FunctionCall': {
90 | let args = [];
91 | for(let i in tree.FunctionCall.args) {
92 | args.push(this.parseTree(tree.FunctionCall.args[i]));
93 | }
94 | return this.builder.process(tree.FunctionCall.name, args);
95 | }
96 | case 'Binary':
97 | return this.addOperatorProcess(
98 | tree.Binary.operator,
99 | this.parseTree(tree.Binary.left),
100 | this.parseTree(tree.Binary.right)
101 | );
102 | case 'Unary': {
103 | let val = this.parseTree(tree.Unary.expression);
104 | if (tree.Unary.operator === '-') {
105 | if (typeof val === 'number') {
106 | return -val;
107 | }
108 | else {
109 | return this.addOperatorProcess('*', -1, val);
110 | }
111 | }
112 | else {
113 | return val;
114 | }
115 | }
116 | default:
117 | throw new Error('Operation ' + key + ' not supported.');
118 | }
119 | }
120 |
121 | /**
122 | * Gets the reference for a value, e.g. from_node or from_parameter.
123 | *
124 | * @protected
125 | * @param {*} value
126 | * @returns {*}
127 | */
128 | getRef(value) {
129 | // Convert native data types
130 | if (value === 'true') {
131 | return true;
132 | }
133 | else if (value === 'false') {
134 | return false;
135 | }
136 | else if (value === 'null') {
137 | return null;
138 | }
139 |
140 | // Output of a process
141 | if (typeof value === 'string' && value.startsWith('#')) {
142 | let nodeId = value.substring(1);
143 | if (nodeId in this.builder.nodes) {
144 | return { from_node: nodeId };
145 | }
146 | }
147 |
148 | let callbackParams = this.builder.getParentCallbackParameters();
149 | // Array labels / indices
150 | if (typeof value === 'string' && callbackParams.length > 0) {
151 | let prefix = value.match(/^\$+/);
152 | let count = prefix ? prefix[0].length : 0;
153 | if (count > 0 && callbackParams.length >= count) {
154 | let ref = value.substring(count);
155 | return callbackParams[count-1][ref];
156 | }
157 | }
158 |
159 | // Everything else is a parameter
160 | let parameter = new Parameter(value);
161 | // Add new parameter if it doesn't exist
162 | this.builder.addParameter(parameter);
163 | return parameter;
164 | }
165 |
166 | /**
167 | * Adds a process node for an operator like +, -, *, / etc.
168 | *
169 | * @param {string} operator - The operator.
170 | * @param {number|object.} left - The left part for the operator.
171 | * @param {number|object.} right - The right part for the operator.
172 | * @returns {BuilderNode}
173 | * @throws {Error}
174 | */
175 | addOperatorProcess(operator, left, right) {
176 | let processName = Formula.operatorMapping[operator];
177 | let process = this.builder.spec(processName);
178 | if (processName && process) {
179 | let args = {};
180 | if (!Array.isArray(process.parameters) || process.parameters.length < 2) {
181 | throw new Error("Process for operator " + operator + " must have at least two parameters");
182 | }
183 | args[process.parameters[0].name || 'x'] = left;
184 | args[process.parameters[1].name || 'y'] = right;
185 | return this.builder.process(processName, args);
186 | }
187 | else {
188 | throw new Error('Operator ' + operator + ' not supported');
189 | }
190 | }
191 |
192 | }
193 |
194 | /**
195 | * List of supported operators.
196 | *
197 | * All operators must have the parameters be name x and y.
198 | *
199 | * The key is the mathematical operator, the value is the process identifier.
200 | *
201 | * @type {object.}
202 | */
203 | Formula.operatorMapping = {
204 | "-": "subtract",
205 | "+": "add",
206 | "/": "divide",
207 | "*": "multiply",
208 | "^": "power"
209 | };
210 |
211 | module.exports = Formula;
212 |
--------------------------------------------------------------------------------
/src/builder/node.js:
--------------------------------------------------------------------------------
1 | const Utils = require("@openeo/js-commons/src/utils");
2 | const Parameter = require("./parameter");
3 |
4 | /**
5 | * A class that represents a process node and also a result from a process.
6 | */
7 | class BuilderNode {
8 |
9 | /**
10 | * Creates a new process node for the builder.
11 | *
12 | * @param {Builder} parent
13 | * @param {string} processId
14 | * @param {object.} [processArgs={}]
15 | * @param {?string} [processDescription=null]
16 | * @param {?string} [processNamespace=null]
17 | */
18 | constructor(parent, processId, processArgs = {}, processDescription = null, processNamespace = null) {
19 | /**
20 | * The parent builder.
21 | * @type {Builder}
22 | */
23 | this.parent = parent;
24 |
25 | /**
26 | * The specification of the process associated with this node.
27 | * @type {Process}
28 | * @readonly
29 | */
30 | this.spec = this.parent.spec(processId, processNamespace);
31 | if (!this.spec) {
32 | throw new Error("Process doesn't exist: " + processId);
33 | }
34 |
35 | /**
36 | * The unique identifier for the node (not the process ID!).
37 | * @type {string}
38 | */
39 | this.id = parent.generateId(processId);
40 | /**
41 | * The namespace of the process - EXPERIMENTAL!
42 | * @type {string}
43 | */
44 | this.namespace = processNamespace;
45 | /**
46 | * The arguments for the process.
47 | * @type {object.}
48 | */
49 | this.arguments = Array.isArray(processArgs) ? this.namedArguments(processArgs) : processArgs;
50 | /**
51 | * @ignore
52 | */
53 | this._description = processDescription;
54 | /**
55 | * Is this the result node?
56 | * @type {boolean}
57 | */
58 | this.result = false;
59 |
60 | this.addParametersToProcess(this.arguments);
61 | }
62 |
63 | /**
64 | * Converts a sorted array of arguments to an object with the respective parameter names.
65 | *
66 | * @param {Array.>} processArgs
67 | * @returns {object.}
68 | * @throws {Error}
69 | */
70 | namedArguments(processArgs) {
71 | if (processArgs.length > (this.spec.parameters || []).length) {
72 | throw new Error("More arguments specified than parameters available.");
73 | }
74 | let obj = {};
75 | if (Array.isArray(this.spec.parameters)) {
76 | for(let i = 0; i < this.spec.parameters.length; i++) {
77 | obj[this.spec.parameters[i].name] = processArgs[i];
78 | }
79 | }
80 | return obj;
81 | }
82 |
83 | /**
84 | * Checks the arguments given for parameters and add them to the process.
85 | *
86 | * @param {object.|Array} processArgs
87 | */
88 | addParametersToProcess(processArgs) {
89 | for(let key in processArgs) {
90 | let arg = processArgs[key];
91 | if (arg instanceof Parameter) {
92 | if (Utils.isObject(arg.spec.schema)) {
93 | this.parent.addParameter(arg.spec);
94 | }
95 | }
96 | else if (arg instanceof BuilderNode) {
97 | this.addParametersToProcess(arg.arguments);
98 | }
99 | else if (Array.isArray(arg) || Utils.isObject(arg)) {
100 | this.addParametersToProcess(arg);
101 | }
102 | }
103 | }
104 |
105 | /**
106 | * Gets/Sets a description for the node.
107 | *
108 | * Can be used in a variety of ways:
109 | *
110 | * By default, this is a function:
111 | * `node.description()` - Returns the description.
112 | * `node.description("foo")` - Sets the description to "foo". Returns the node itself for method chaining.
113 | *
114 | * You can also "replace" the function (not supported in TypeScript!),
115 | * then it acts as normal property and the function is not available any longer:
116 | * `node.description = "foo"` - Sets the description to "foo".
117 | * Afterwards you can call `node.description` as normal object property.
118 | *
119 | * @param {string|undefined} description - Optional: If given, set the value.
120 | * @returns {string|BuilderNode}
121 | */
122 | description(description) {
123 | if (typeof description === 'undefined') {
124 | return this._description;
125 | }
126 | else {
127 | this._description = description;
128 | return this;
129 | }
130 | }
131 |
132 | /**
133 | * Converts the given argument into something serializable...
134 | *
135 | * @protected
136 | * @param {*} arg - Argument
137 | * @param {string} name - Parameter name
138 | * @returns {*}
139 | */
140 | exportArgument(arg, name) {
141 | const Formula = require('./formula');
142 | if (Utils.isObject(arg)) {
143 | if (arg instanceof BuilderNode || arg instanceof Parameter) {
144 | return arg.ref();
145 | }
146 | else if (arg instanceof Formula) {
147 | let builder = this.createBuilder(this, name);
148 | arg.setBuilder(builder);
149 | arg.generate();
150 | return builder.toJSON();
151 | }
152 | else if (arg instanceof Date) {
153 | return arg.toISOString();
154 | }
155 | else if (typeof arg.toJSON === 'function') {
156 | return arg.toJSON();
157 | }
158 | else {
159 | let obj = {};
160 | for(let key in arg) {
161 | if (typeof arg[key] !== 'undefined') {
162 | obj[key] = this.exportArgument(arg[key], name);
163 | }
164 | }
165 | return obj;
166 | }
167 | }
168 | else if (Array.isArray(arg)) {
169 | return arg.map(element => this.exportArgument(element), name);
170 | }
171 | // export child process graph
172 | else if (typeof arg === 'function') {
173 | return this.exportCallback(arg, name);
174 | }
175 | else {
176 | return arg;
177 | }
178 | }
179 |
180 | /**
181 | * Creates a new Builder, usually for a callback.
182 | *
183 | * @protected
184 | * @param {?BuilderNode} [parentNode=null]
185 | * @param {?string} [parentParameter=null]
186 | * @returns {BuilderNode}
187 | */
188 | createBuilder(parentNode = null, parentParameter = null) {
189 | const Builder = require('./builder');
190 | let builder = new Builder(this.parent.processes, this.parent);
191 | if (parentNode !== null && parentParameter !== null) {
192 | builder.setParent(parentNode, parentParameter);
193 | }
194 | return builder;
195 | }
196 |
197 | /**
198 | * Returns the serializable process for the callback function given.
199 | *
200 | * @protected
201 | * @param {Function} arg - callback function
202 | * @param {string} name - Parameter name
203 | * @returns {object.}
204 | * @throws {Error}
205 | */
206 | exportCallback(arg, name) {
207 | let builder = this.createBuilder(this, name);
208 | let params = builder.getParentCallbackParameters();
209 | // Bind builder to this, so that this.xxx can be used for processes
210 | // Also pass builder as last parameter so that we can grab it in arrow functions
211 | let node = arg.bind(builder)(...params, builder);
212 | if (Array.isArray(node) && builder.supports('array_create')) {
213 | node = builder.array_create(node);
214 | }
215 | else if (!Utils.isObject(node) && builder.supports('constant')) {
216 | node = builder.constant(node);
217 | }
218 | if (node instanceof BuilderNode) {
219 | node.result = true;
220 | return builder.toJSON();
221 | }
222 | else {
223 | throw new Error("Callback must return BuilderNode");
224 | }
225 | }
226 |
227 | /**
228 | * Returns a JSON serializable representation of the data that is API compliant.
229 | *
230 | * @returns {object.}
231 | */
232 | toJSON() {
233 | let obj = {
234 | process_id: this.spec.id,
235 | arguments: {}
236 | };
237 | if (this.namespace) {
238 | obj.namespace = this.namespace;
239 | }
240 | for(let name in this.arguments) {
241 | if (typeof this.arguments[name] !== 'undefined') {
242 | obj.arguments[name] = this.exportArgument(this.arguments[name], name);
243 | }
244 | }
245 | if (typeof this.description !== 'function') {
246 | obj.description = this.description;
247 | }
248 | else if (typeof this._description === 'string') {
249 | obj.description = this._description;
250 | }
251 | if (this.result) {
252 | obj.result = true;
253 | }
254 | return obj;
255 | }
256 |
257 | /**
258 | * Returns the reference object for this node.
259 | *
260 | * @returns {FromNode}
261 | */
262 | ref() {
263 | return { from_node: this.id };
264 | }
265 |
266 | }
267 |
268 | module.exports = BuilderNode;
269 |
--------------------------------------------------------------------------------
/src/builder/parameter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * A class that represents a process parameter.
5 | *
6 | * This is used for two things:
7 | * 1. You can create process parameters (placeholders) with `new Parameter()`.
8 | * 2. This is passed to functions for the parameters of the sub-process.
9 | *
10 | * For the second case, you can access array elements referred to by the parameter
11 | * with a simplified notation:
12 | *
13 | * ```
14 | * function(data, context) {
15 | * data['B1'] // Accesses the B1 element of the array by label
16 | * data[1] // Accesses the second element of the array by index
17 | * }
18 | * ```
19 | *
20 | * Those array calls create corresponding `array_element` nodes in the process. So it's
21 | * equivalent to
22 | * `this.array_element(data, undefined, 'B1')` or
23 | * `this.array_element(data, 1)` respectively.
24 | *
25 | * Simple access to numeric labels is not supported. You need to use `array_element` directly, e.g.
26 | * `this.array_element(data, undefined, 1)`.
27 | */
28 | class Parameter {
29 |
30 | /**
31 | * Creates a new parameter instance, but proxies calls to it
32 | * so that array access is possible (see class description).
33 | *
34 | * @static
35 | * @param {Builder} builder
36 | * @param {string} parameterName
37 | * @returns {Proxy}
38 | */
39 | static create(builder, parameterName) {
40 | let parameter = new Parameter(parameterName, null);
41 | if (typeof Proxy !== "undefined") {
42 | return new Proxy(parameter, {
43 | // @ts-ignore
44 | nodeCache: {},
45 | /**
46 | * Getter for array access (see class description).
47 | *
48 | * @ignore
49 | * @param {object} target
50 | * @param {string|number|symbol} name
51 | * @param {?*} receiver
52 | * @returns {*}
53 | */
54 | get(target, name, receiver) {
55 | if (!Reflect.has(target, name)) {
56 | // @ts-ignore
57 | if (!this.nodeCache[name]) {
58 | let args = {
59 | data: parameter
60 | };
61 | if (typeof name === 'string' && name.match(/^(0|[1-9]\d*)$/)) {
62 | args.index = parseInt(name, 10);
63 | }
64 | else {
65 | args.label = name;
66 | }
67 | // We assume array_element exists
68 | // @ts-ignore
69 | this.nodeCache[name] = builder.process("array_element", args);
70 | }
71 |
72 | // @ts-ignore
73 | return this.nodeCache[name];
74 | }
75 | return Reflect.get(target, name, receiver);
76 | },
77 | /**
78 | * Setter for array access.
79 | *
80 | * Usually fails as write access to arrays is not supported.
81 | *
82 | * @ignore
83 | * @param {object} target
84 | * @param {string|number|symbol} name
85 | * @param {*} value
86 | * @param {?*} receiver
87 | * @returns {boolean}
88 | */
89 | set(target, name, value, receiver) {
90 | if (!Reflect.has(target, name)) {
91 | throw new Error('Simplified array access is read-only');
92 | }
93 | return Reflect.set(target, name, value, receiver);
94 | }
95 | });
96 | }
97 | else {
98 | throw new Error('Simplified array access not supported, use array_element directly');
99 | }
100 | }
101 |
102 | /**
103 | * Creates a new process parameter.
104 | *
105 | * @param {string} name - Name of the parameter.
106 | * @param {object.|string} schema - The schema for the parameter. Can be either an object compliant to JSON Schema or a string with a JSON Schema compliant data type, e.g. `string`.
107 | * @param {string} description - A description for the parameter
108 | * @param {*} defaultValue - An optional default Value for the parameter. If set, make the parameter optional. If not set, the parameter is required. Defaults to `undefined`.
109 | */
110 | constructor(name, schema = {}, description = "", defaultValue = undefined) {
111 | this.name = name;
112 | this.spec = {
113 | name: name,
114 | schema: typeof schema === 'string' ? { type: schema } : schema,
115 | description: description,
116 | };
117 | // No support for experimental and deprecated yet
118 | if (typeof defaultValue !== 'undefined') {
119 | this.spec.optional = true;
120 | this.spec.default = defaultValue;
121 | }
122 | }
123 |
124 | /**
125 | * Returns a JSON serializable representation of the data that is API compliant.
126 | *
127 | * @returns {object.}
128 | */
129 | toJSON() {
130 | return this.spec;
131 | }
132 |
133 | /**
134 | * Returns the reference object for this parameter.
135 | *
136 | * @returns {FromParameter}
137 | */
138 | ref() {
139 | return { from_parameter: this.name };
140 | }
141 |
142 | }
143 |
144 | module.exports = Parameter;
145 |
--------------------------------------------------------------------------------
/src/capabilities.js:
--------------------------------------------------------------------------------
1 | const Utils = require('@openeo/js-commons/src/utils');
2 |
3 | const FEATURE_MAP = {
4 | // Discovery
5 | capabilities: true,
6 | listFileTypes: 'get /file_formats',
7 | listServiceTypes: 'get /service_types',
8 | listUdfRuntimes: 'get /udf_runtimes',
9 | // Collections
10 | listCollections: 'get /collections',
11 | describeCollection: 'get /collections/{}',
12 | listCollectionItems: 'get /collections/{}/items',
13 | describeCollectionItem: 'get /collections/{}/items/{}',
14 | describeCollectionQueryables: 'get /collections/{}/queryables',
15 | // Processes
16 | listProcesses: 'get /processes',
17 | describeProcess: 'get /processes',
18 | // Auth / Account
19 | listAuthProviders: true,
20 | authenticateOIDC: 'get /credentials/oidc',
21 | authenticateBasic: 'get /credentials/basic',
22 | describeAccount: 'get /me',
23 | // Files
24 | listFiles: 'get /files',
25 | getFile: 'get /files', // getFile is a virtual function and doesn't request an endpoint, but get /files should be available nevertheless.
26 | uploadFile: 'put /files/{}',
27 | downloadFile: 'get /files/{}',
28 | deleteFile: 'delete /files/{}',
29 | // User-Defined Processes
30 | validateProcess: 'post /validation',
31 | listUserProcesses: 'get /process_graphs',
32 | describeUserProcess: 'get /process_graphs/{}',
33 | getUserProcess: 'get /process_graphs/{}',
34 | setUserProcess: 'put /process_graphs/{}',
35 | replaceUserProcess: 'put /process_graphs/{}',
36 | deleteUserProcess: 'delete /process_graphs/{}',
37 | // Processing
38 | computeResult: 'post /result',
39 | listJobs: 'get /jobs',
40 | createJob: 'post /jobs',
41 | listServices: 'get /services',
42 | createService: 'post /services',
43 | getJob: 'get /jobs/{}',
44 | describeJob: 'get /jobs/{}',
45 | updateJob: 'patch /jobs/{}',
46 | deleteJob: 'delete /jobs/{}',
47 | estimateJob: 'get /jobs/{}/estimate',
48 | debugJob: 'get /jobs/{}/logs',
49 | startJob: 'post /jobs/{}/results',
50 | stopJob: 'delete /jobs/{}/results',
51 | listResults: 'get /jobs/{}/results',
52 | downloadResults: 'get /jobs/{}/results',
53 | // Web services
54 | describeService: 'get /services/{}',
55 | getService: 'get /services/{}',
56 | updateService: 'patch /services/{}',
57 | deleteService: 'delete /services/{}',
58 | debugService: 'get /services/{}/logs',
59 | };
60 |
61 | /**
62 | * Capabilities of a back-end.
63 | */
64 | class Capabilities {
65 |
66 | /**
67 | * Creates a new Capabilities object from an API-compatible JSON response.
68 | *
69 | * @param {object.} data - A capabilities response compatible to the API specification for `GET /`.
70 | * @throws {Error}
71 | */
72 | constructor(data) {
73 |
74 | /**
75 | * @private
76 | * @type {object.}
77 | */
78 | this.data = data;
79 |
80 | /**
81 | * @private
82 | * @ignore
83 | * @type {object.}
84 | */
85 | this.featureMap = FEATURE_MAP;
86 |
87 | /**
88 | * @private
89 | * @type {Array.}
90 | */
91 | this.features = [];
92 |
93 | this.validate();
94 | this.init();
95 | }
96 |
97 | /**
98 | * Validates the capabilities.
99 | *
100 | * Throws an error in case of an issue, otherwise just passes.
101 | *
102 | * @protected
103 | * @throws {Error}
104 | */
105 | validate() {
106 | if(!Utils.isObject(this.data)) {
107 | throw new Error("No capabilities retrieved.");
108 | }
109 | else if(!this.data.api_version) {
110 | throw new Error("Invalid capabilities: No API version retrieved");
111 | }
112 | else if(!Array.isArray(this.data.endpoints)) {
113 | throw new Error("Invalid capabilities: No endpoints retrieved");
114 | }
115 | }
116 |
117 | /**
118 | * Initializes the class.
119 | *
120 | * @protected
121 | */
122 | init() {
123 | this.features = this.data.endpoints
124 | // Flatten features and simplify variables to be compatible with the feature map.
125 | .map(e => e.methods.map(method => {
126 | const path = e.path.replace(/\{[^}]+\}/g, '{}');
127 | return `${method} ${path}`.toLowerCase();
128 | }))
129 | .reduce((flat, next) => flat.concat(next), []); // .flat(1) once browser support for ECMAscript 10/2019 gets better
130 | }
131 |
132 | /**
133 | * Returns the capabilities response as a JSON serializable representation of the data that is API compliant.
134 | *
135 | * @returns {object.} - A reference to the capabilities response.
136 | */
137 | toJSON() {
138 | return this.data;
139 | }
140 |
141 | /**
142 | * Returns the openEO API version implemented by the back-end.
143 | *
144 | * @returns {string} openEO API version number.
145 | */
146 | apiVersion() {
147 | return this.data.api_version;
148 | }
149 |
150 | /**
151 | * Returns the back-end version number.
152 | *
153 | * @returns {string} openEO back-end version number.
154 | */
155 | backendVersion() {
156 | return this.data.backend_version;
157 | }
158 |
159 | /**
160 | * Returns the back-end title.
161 | *
162 | * @returns {string} Title
163 | */
164 | title() {
165 | return typeof this.data.title === 'string' ? this.data.title : "";
166 | }
167 |
168 | /**
169 | * Returns the back-end description.
170 | *
171 | * @returns {string} Description
172 | */
173 | description() {
174 | return typeof this.data.description === 'string' ? this.data.description : "";
175 | }
176 |
177 | /**
178 | * Is the back-end suitable for use in production?
179 | *
180 | * @returns {boolean} true = stable/production, false = unstable
181 | */
182 | isStable() {
183 | return this.data.production === true;
184 | }
185 |
186 | /**
187 | * Returns the links.
188 | *
189 | * @returns {Array. } Array of link objects (href, title, rel, type)
190 | */
191 | links() {
192 | return Array.isArray(this.data.links) ? this.data.links : [];
193 | }
194 |
195 | /**
196 | * Returns list of backends in the federation.
197 | *
198 | * @returns {Array.} Array of backends
199 | */
200 | listFederation() {
201 | let federation = [];
202 | if (Utils.isObject(this.data.federation)) {
203 | // convert to array and add keys as `id` property
204 | for(const [key, backend] of Object.entries(this.data.federation)) {
205 | // fresh object to avoid `id` showing up in this.data.federation
206 | federation.push({ id: key, ...backend });
207 | }
208 | }
209 | return federation;
210 | }
211 |
212 | /**
213 | * Given just the string ID of a backend within the federation, returns that backend's full details as a FederationBackend object.
214 | *
215 | * @param {string} backendId - The ID of a backend within the federation
216 | * @returns {FederationBackend} The full details of the backend
217 | */
218 | getFederationBackend(backendId) {
219 | // Add `id` property to make it a proper FederationBackend object
220 | // If backendId doesn't exist in this.data.federation, will contain just the `id` field (intended behaviour)
221 | return { id: backendId, ...this.data.federation[backendId] }
222 | }
223 |
224 | /**
225 | * Given a list of string IDs of backends within the federation, returns those backends' full details as FederationBackend objects.
226 | *
227 | * @param {Array} backendIds - The IDs of backends within the federation
228 | * @returns {Array} An array in the same order as the input, containing for each position the full details of the backend
229 | */
230 | getFederationBackends(backendIds) {
231 | // Let 'single case' function do the work, but pass `this` so that `this.data.federation` can be accessed in the callback context
232 | return backendIds.map(this.getFederationBackend, this);
233 | }
234 |
235 | /**
236 | * Lists all supported features.
237 | *
238 | * @returns {Array.} An array of supported features.
239 | */
240 | listFeatures() {
241 | let features = [];
242 | for(let feature in this.featureMap) {
243 | if (this.featureMap[feature] === true || this.features.includes(this.featureMap[feature])) {
244 | features.push(feature);
245 | }
246 | }
247 | return features.sort();
248 | }
249 |
250 | /**
251 | * Check whether a feature is supported by the back-end.
252 | *
253 | * @param {string} methodName - A feature name (corresponds to the JS client method names, see also the feature map for allowed values).
254 | * @returns {boolean} `true` if the feature is supported, otherwise `false`.
255 | */
256 | hasFeature(methodName) {
257 | let feature = this.featureMap[methodName];
258 | if (typeof feature === 'string') {
259 | feature = feature.toLowerCase();
260 | }
261 | return feature === true || this.features.some(e => e === feature);
262 | }
263 |
264 | /**
265 | * Get the billing currency.
266 | *
267 | * @returns {string | null} The billing currency or `null` if not available.
268 | */
269 | currency() {
270 | return (Utils.isObject(this.data.billing) && typeof this.data.billing.currency === 'string' ? this.data.billing.currency : null);
271 | }
272 |
273 | /**
274 | * List all billing plans.
275 | *
276 | * @returns {Array.} Billing plans
277 | */
278 | listPlans() {
279 | if (Utils.isObject(this.data.billing) && Array.isArray(this.data.billing.plans)) {
280 | let defaultPlan = typeof this.data.billing.default_plan === 'string' ? this.data.billing.default_plan.toLowerCase() : null;
281 | return this.data.billing.plans.map(plan => {
282 | let addition = {
283 | default: (defaultPlan === plan.name.toLowerCase())
284 | };
285 | return Object.assign({}, plan, addition);
286 | });
287 | }
288 | else {
289 | return [];
290 | }
291 | }
292 |
293 | /**
294 | * Migrates a response, if required.
295 | *
296 | * @param {AxiosResponse} response
297 | * @protected
298 | * @returns {AxiosResponse}
299 | */
300 | migrate(response) { // eslint-disable-line no-unused-vars
301 | return response;
302 | }
303 | }
304 |
305 | module.exports = Capabilities;
306 |
--------------------------------------------------------------------------------
/src/env.js:
--------------------------------------------------------------------------------
1 | let Environment = null;
2 | if (typeof window === 'undefined') {
3 | Environment = require('./node');
4 | }
5 | else {
6 | Environment = require('./browser');
7 | }
8 | /**
9 | * The axios instance to use for HTTP requests.
10 | *
11 | * @type {object}
12 | * @static
13 | */
14 | Environment.axios = require('axios');
15 |
16 | module.exports = Environment;
--------------------------------------------------------------------------------
/src/filetypes.js:
--------------------------------------------------------------------------------
1 | const Utils = require('@openeo/js-commons/src/utils');
2 |
3 | /**
4 | * Manages the files types supported by the back-end.
5 | */
6 | class FileTypes {
7 |
8 | /**
9 | * Creates a new FileTypes object from an API-compatible JSON response.
10 | *
11 | * @param {FileTypesAPI} data - A capabilities response compatible to the API specification for `GET /file_formats`.
12 | */
13 | constructor(data) {
14 | /**
15 | * @protected
16 | * @type {FileTypesAPI}
17 | */
18 | this.data = {
19 | input: {},
20 | output: {}
21 | };
22 | if(!Utils.isObject(data)) {
23 | return;
24 | }
25 | for(let io of ['input', 'output']) {
26 | for(let type in data[io]) {
27 | if(!Utils.isObject(data[io])) {
28 | continue;
29 | }
30 | this.data[io][type.toUpperCase()] = data[io][type];
31 | }
32 | }
33 | /**
34 | * A list of backends from the federation that are missing in the response data.
35 | *
36 | * @public
37 | * @type {Array.}
38 | */
39 | this['federation:missing'] = data['federation:missing'];
40 | }
41 |
42 | /**
43 | * Returns the file types response as a JSON serializable representation of the data that is API compliant.
44 | *
45 | * @returns {FileTypesAPI}
46 | */
47 | toJSON() {
48 | return this.data;
49 | }
50 |
51 | /**
52 | * Returns the input file formats.
53 | *
54 | * @returns {object.}
55 | */
56 | getInputTypes() {
57 | return this.data.input;
58 | }
59 |
60 | /**
61 | * Returns the output file formats.
62 | *
63 | * @returns {object.}
64 | */
65 | getOutputTypes() {
66 | return this.data.output;
67 | }
68 |
69 | /**
70 | * Returns a single input file format for a given identifier.
71 | *
72 | * Returns null if no input file format was found for the given identifier.
73 | *
74 | * @param {string} type - Case-insensitive file format identifier
75 | * @returns {FileType | null}
76 | */
77 | getInputType(type) {
78 | return this._findType(type, 'input');
79 | }
80 |
81 | /**
82 | * Returns a single output file format for a given identifier.
83 | *
84 | * Returns null if no output file format was found for the given identifier.
85 | *
86 | * @param {string} type - Case-insensitive file format identifier
87 | * @returns {FileType | null}
88 | */
89 | getOutputType(type) {
90 | return this._findType(type, 'output');
91 | }
92 |
93 | /**
94 | * Get a file type object from the list of input or output file formats.
95 | *
96 | * @param {string} type - Identifier of the file type
97 | * @param {string} io - Either `input` or `output`
98 | * @returns {FileType | null}
99 | * @protected
100 | */
101 | _findType(type, io) {
102 | type = type.toUpperCase();
103 | if (type in this.data[io]) {
104 | return this.data[io][type];
105 | }
106 | return null;
107 | }
108 |
109 | }
110 |
111 | module.exports = FileTypes;
112 |
--------------------------------------------------------------------------------
/src/job.js:
--------------------------------------------------------------------------------
1 | const Environment = require('./env');
2 | const BaseEntity = require('./baseentity');
3 | const Logs = require('./logs');
4 | const Utils = require('@openeo/js-commons/src/utils');
5 | const StacMigrate = require('@radiantearth/stac-migrate');
6 |
7 | const STOP_STATUS = ['finished', 'canceled', 'error'];
8 |
9 | /**
10 | * A Batch Job.
11 | *
12 | * @augments BaseEntity
13 | */
14 | class Job extends BaseEntity {
15 |
16 | /**
17 | * Creates an object representing a batch job stored at the back-end.
18 | *
19 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
20 | * @param {string} jobId - The batch job ID.
21 | */
22 | constructor(connection, jobId) {
23 | super(connection, ["id", "title", "description", "process", "status", "progress", "created", "updated", "plan", "costs", "budget", "usage", ["log_level", "logLevel"], "links"]);
24 | /**
25 | * The identifier of the batch job.
26 | * @public
27 | * @readonly
28 | * @type {string}
29 | */
30 | this.id = jobId;
31 | /**
32 | * @public
33 | * @readonly
34 | * @type {?string}
35 | */
36 | this.title = undefined;
37 | /**
38 | * @public
39 | * @readonly
40 | * @type {?string}
41 | */
42 | this.description = undefined;
43 | /**
44 | * The process chain to be executed.
45 | * @public
46 | * @readonly
47 | * @type {?Process}
48 | */
49 | this.process = undefined;
50 | /**
51 | * The current status of a batch job.
52 | * One of "created", "queued", "running", "canceled", "finished" or "error".
53 | * @public
54 | * @readonly
55 | * @type {?string}
56 | */
57 | this.status = undefined;
58 | /**
59 | * Indicates the process of a running batch job in percent.
60 | * @public
61 | * @readonly
62 | * @type {?number}
63 | */
64 | this.progress = undefined;
65 | /**
66 | * Date and time of creation, formatted as a RFC 3339 date-time.
67 | * @public
68 | * @readonly
69 | * @type {?string}
70 | */
71 | this.created = undefined;
72 | /**
73 | * Date and time of the last status change, formatted as a RFC 3339 date-time.
74 | * @public
75 | * @readonly
76 | * @type {?string}
77 | */
78 | this.updated = undefined;
79 | /**
80 | * The billing plan to process and charge the batch job with.
81 | * @public
82 | * @readonly
83 | * @type {?string}
84 | */
85 | this.plan = undefined;
86 | /**
87 | * An amount of money or credits in the currency specified by the back-end.
88 | * @public
89 | * @readonly
90 | * @type {?number}
91 | */
92 | this.costs = undefined;
93 | /**
94 | * Maximum amount of costs the request is allowed to produce in the currency specified by the back-end.
95 | * @public
96 | * @readonly
97 | * @type {?number}
98 | */
99 | this.budget = undefined;
100 | }
101 |
102 | /**
103 | * Updates the batch job data stored in this object by requesting the metadata from the back-end.
104 | *
105 | * @async
106 | * @returns {Promise} The update job object (this).
107 | * @throws {Error}
108 | */
109 | async describeJob() {
110 | let response = await this.connection._get('/jobs/' + this.id);
111 | return this.setAll(response.data);
112 | }
113 |
114 | /**
115 | * Modifies the batch job at the back-end and afterwards updates this object, too.
116 | *
117 | * @async
118 | * @param {object} parameters - An object with properties to update, each of them is optional, but at least one of them must be specified. Additional properties can be set if the server supports them.
119 | * @param {Process} parameters.process - A new process.
120 | * @param {string} parameters.title - A new title.
121 | * @param {string} parameters.description - A new description.
122 | * @param {string} parameters.plan - A new plan.
123 | * @param {number} parameters.budget - A new budget.
124 | * @returns {Promise} The updated job object (this).
125 | * @throws {Error}
126 | */
127 | async updateJob(parameters) {
128 | await this.connection._patch('/jobs/' + this.id, this._convertToRequest(parameters));
129 | if (this._supports('describeJob')) {
130 | return await this.describeJob();
131 | }
132 | else {
133 | return this.setAll(parameters);
134 | }
135 | }
136 |
137 | /**
138 | * Deletes the batch job from the back-end.
139 | *
140 | * @async
141 | * @throws {Error}
142 | */
143 | async deleteJob() {
144 | await this.connection._delete('/jobs/' + this.id);
145 | }
146 |
147 | /**
148 | * Calculate an estimate (potentially time/costs/volume) for a batch job.
149 | *
150 | * @async
151 | * @returns {Promise} A response compatible to the API specification.
152 | * @throws {Error}
153 | */
154 | async estimateJob() {
155 | let response = await this.connection._get('/jobs/' + this.id + '/estimate');
156 | return response.data;
157 | }
158 |
159 | /**
160 | * Get logs for the batch job from the back-end.
161 | *
162 | * @param {?string} [level=null] - Minimum level of logs to return.
163 | * @returns {Logs}
164 | */
165 | debugJob(level = null) {
166 | return new Logs(this.connection, '/jobs/' + this.id + '/logs', level);
167 | }
168 |
169 | /**
170 | * Checks for status changes and new log entries every x seconds.
171 | *
172 | * On every status change observed or on new log entries (if supported by the
173 | * back-end and not disabled via `requestLogs`), the callback is executed.
174 | * It may also be executed once at the beginning.
175 | * The callback receives the updated job (this object) and the logs (array) passed.
176 | *
177 | * The monitoring stops once the job has finished, was canceled or errored out.
178 | *
179 | * This is only supported if describeJob is supported by the back-end.
180 | *
181 | * Returns a function that can be called to stop monitoring the job manually.
182 | *
183 | * @param {Function} callback
184 | * @param {number} [interval=60] - Interval between update requests, in seconds as integer.
185 | * @param {boolean} [requestLogs=true] - Enables/Disables requesting logs
186 | * @returns {Function}
187 | * @throws {Error}
188 | */
189 | monitorJob(callback, interval = 60, requestLogs = true) {
190 | if (typeof callback !== 'function' || interval < 1) {
191 | return;
192 | }
193 | let capabilities = this.connection.capabilities();
194 | if (!capabilities.hasFeature('describeJob')) {
195 | throw new Error('Monitoring Jobs not supported by the back-end.');
196 | }
197 |
198 | let lastStatus = this.status;
199 | let intervalId = null;
200 | let logIterator = null;
201 | if (capabilities.hasFeature('debugJob') && requestLogs) {
202 | logIterator = this.debugJob();
203 | }
204 | let monitorFn = async () => {
205 | if (this.getDataAge() > 1) {
206 | await this.describeJob();
207 | }
208 | let logs = logIterator ? await logIterator.nextLogs() : [];
209 | if (lastStatus !== this.status || logs.length > 0) {
210 | callback(this, logs);
211 | }
212 | lastStatus = this.status;
213 | if (STOP_STATUS.includes(this.status)) {
214 | stopFn(); // eslint-disable-line no-use-before-define
215 | }
216 | };
217 | setTimeout(monitorFn, 0);
218 | intervalId = setInterval(monitorFn, interval * 1000);
219 | let stopFn = () => {
220 | if (intervalId) {
221 | clearInterval(intervalId);
222 | intervalId = null;
223 | }
224 | };
225 | return stopFn;
226 | }
227 |
228 | /**
229 | * Starts / queues the batch job for processing at the back-end.
230 | *
231 | * @async
232 | * @returns {Promise} The updated job object (this).
233 | * @throws {Error}
234 | */
235 | async startJob() {
236 | await this.connection._post('/jobs/' + this.id + '/results', {});
237 | if (this._supports('describeJob')) {
238 | return await this.describeJob();
239 | }
240 | return this;
241 | }
242 |
243 | /**
244 | * Stops / cancels the batch job processing at the back-end.
245 | *
246 | * @async
247 | * @returns {Promise} The updated job object (this).
248 | * @throws {Error}
249 | */
250 | async stopJob() {
251 | await this.connection._delete('/jobs/' + this.id + '/results');
252 | if (this._supports('describeJob')) {
253 | return await this.describeJob();
254 | }
255 | return this;
256 | }
257 |
258 | /**
259 | * Retrieves the STAC Item or Collection produced for the job results.
260 | *
261 | * The Item or Collection returned always complies to the latest STAC version (currently 1.0.0).
262 | *
263 | * @async
264 | * @returns {Promise>} The JSON-based response compatible to the API specification, but also including a `costs` property if present in the headers.
265 | * @throws {Error}
266 | */
267 | async getResultsAsStac() {
268 | let response = await this.connection._get('/jobs/' + this.id + '/results');
269 | if (!Utils.isObject(response) || !Utils.isObject(response.data)) {
270 | throw new Error("Results received from the back-end are invalid");
271 | }
272 | let data = StacMigrate.stac(response.data);
273 | if (!Utils.isObject(data.assets)) {
274 | data.assets = {};
275 | }
276 | if (data.type === 'Feature') { // Item
277 | if (typeof response.headers['openeo-costs'] === 'number') {
278 | data.properties.costs = response.headers['openeo-costs'];
279 | }
280 | }
281 | else { // Collection
282 | if (typeof response.headers['openeo-costs'] === 'number') {
283 | data.costs = response.headers['openeo-costs'];
284 | }
285 | }
286 |
287 | return data;
288 | }
289 |
290 | /**
291 | * Retrieves download links.
292 | *
293 | * @async
294 | * @returns {Promise>} A list of links (object with href, rel, title, type and roles).
295 | * @throws {Error}
296 | */
297 | async listResults() {
298 | let item = await this.getResultsAsStac();
299 | if (Utils.isObject(item.assets)) {
300 | return Object.values(item.assets);
301 | }
302 | else {
303 | return [];
304 | }
305 | }
306 |
307 | /**
308 | * Downloads the results to the specified target folder. The specified target folder must already exist!
309 | *
310 | * NOTE: This method is only supported in a NodeJS environment. In a browser environment this method throws an exception!
311 | *
312 | * @async
313 | * @param {string} targetFolder - A target folder to store the file to, which must already exist.
314 | * @returns {Promise|void>} Depending on the environment: A list of file paths of the newly created files (Node), throws in Browsers.
315 | * @throws {Error}
316 | */
317 | async downloadResults(targetFolder) {
318 | let list = await this.listResults();
319 | return await Environment.downloadResults(this.connection, list, targetFolder);
320 | }
321 | }
322 |
323 | module.exports = Job;
324 |
--------------------------------------------------------------------------------
/src/logs.js:
--------------------------------------------------------------------------------
1 | const Utils = require('@openeo/js-commons/src/utils');
2 |
3 | /**
4 | * Interface to loop through the logs.
5 | */
6 | class Logs {
7 |
8 | /**
9 | * Creates a new Logs instance to retrieve logs from a back-end.
10 | *
11 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
12 | * @param {string} endpoint - The relative endpoint to request the logs from, usually `/jobs/.../logs` or `/services/.../logs` with `...` being the actual job or service id.
13 | * @param {?string} [level=null] - Minimum level of logs to return.
14 | */
15 | constructor(connection, endpoint, level = null) {
16 | /**
17 | * @protected
18 | * @type {Connection}
19 | */
20 | this.connection = connection;
21 | /**
22 | * @protected
23 | * @type {string}
24 | */
25 | this.endpoint = endpoint;
26 | /**
27 | * @protected
28 | * @type {string}
29 | */
30 | this.lastId = "";
31 | /**
32 | * @protected
33 | * @type {?string}
34 | */
35 | this.level = level;
36 | /**
37 | * @protected
38 | * @type {Set}
39 | */
40 | this.missing = new Set();
41 | }
42 |
43 | /**
44 | * Retrieves the next log entries since the last request.
45 | *
46 | * Retrieves log entries only.
47 | *
48 | * @async
49 | * @param {number} limit - The number of log entries to retrieve per request, as integer.
50 | * @returns {Promise>}
51 | */
52 | async nextLogs(limit = null) {
53 | let response = await this.next(limit);
54 | return Array.isArray(response.logs) ? response.logs : [];
55 | }
56 |
57 | /**
58 | * Retrieves the backend identifiers that are (partially) missing in the logs.
59 | *
60 | * This is only filled after the first request using `nextLogs` or `next`.
61 | *
62 | * @returns {Array.}
63 | * @see {Logs#nextLogs}
64 | * @see {Logs#next}
65 | */
66 | getMissingBackends() {
67 | return Array.from(this.missing);
68 | }
69 |
70 | /**
71 | * Retrieves the next log entries since the last request.
72 | *
73 | * Retrieves the full response compliant to the API, including log entries and links.
74 | *
75 | * @async
76 | * @param {number} limit - The number of log entries to retrieve per request, as integer.
77 | * @returns {Promise}
78 | */
79 | async next(limit = null) {
80 | let query = {
81 | offset: this.lastId
82 | };
83 | if (limit > 0) {
84 | query.limit = limit;
85 | }
86 | if (this.level) {
87 | query.level = this.level;
88 | }
89 | let response = await this.connection._get(this.endpoint, query);
90 | if (Array.isArray(response.data.logs) && response.data.logs.length > 0) {
91 | response.data.logs = response.data.logs.filter(log => Utils.isObject(log) && typeof log.id === 'string');
92 | this.lastId = response.data.logs[response.data.logs.length - 1].id;
93 | }
94 | else {
95 | response.data.logs = [];
96 | }
97 |
98 | response.data.links = Array.isArray(response.data.links) ? response.data.links : [];
99 |
100 | if (Array.isArray(response.data["federation:missing"])) {
101 | response.data["federation:missing"].forEach(backend => this.missing.add(backend));
102 | }
103 |
104 | return response.data;
105 | }
106 |
107 | }
108 |
109 | module.exports = Logs;
110 |
--------------------------------------------------------------------------------
/src/node.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const url = require("url");
3 | const path = require("path");
4 | const Stream = require('stream');
5 |
6 | /**
7 | * Platform dependant utilities for the openEO JS Client.
8 | *
9 | * Node.js implementation, don't use in other environments.
10 | *
11 | * @hideconstructor
12 | */
13 | class Environment {
14 |
15 | /**
16 | * Returns the name of the Environment, here `Node`.
17 | *
18 | * @returns {string}
19 | * @static
20 | */
21 | static getName() {
22 | return 'Node';
23 | }
24 |
25 | /**
26 | * Returns the URL of the server instance.
27 | *
28 | * @returns {string}
29 | * @static
30 | */
31 | static getUrl() {
32 | return Environment.url;
33 | }
34 |
35 | /**
36 | * Sets the URL of the server instance.
37 | *
38 | * @param {string} uri
39 | * @static
40 | */
41 | static setUrl(uri) {
42 | Environment.url = uri;
43 | }
44 |
45 | /**
46 | * Handles errors from the API that are returned as Streams.
47 | *
48 | * @ignore
49 | * @static
50 | * @param {Stream.Readable} error
51 | * @returns {Promise}
52 | */
53 | static handleErrorResponse(error) {
54 | return new Promise((resolve, reject) => {
55 | let chunks = [];
56 | error.response.data.on("data", chunk => chunks.push(chunk));
57 | error.response.data.on("error", streamError => reject(streamError));
58 | error.response.data.on("end", () => resolve(JSON.parse(Buffer.concat(chunks).toString())));
59 | });
60 | }
61 |
62 | /**
63 | * Returns how binary responses from the servers are returned (`stream` or `blob`).
64 | *
65 | * @returns {string}
66 | * @static
67 | */
68 | static getResponseType() {
69 | return 'stream';
70 | }
71 |
72 | /**
73 | * Encodes a string into Base64 encoding.
74 | *
75 | * @static
76 | * @param {string|Buffer} str - String to encode.
77 | * @returns {string} String encoded in Base64.
78 | */
79 | static base64encode(str) {
80 | let buffer;
81 | if (str instanceof Buffer) {
82 | buffer = str;
83 | } else {
84 | buffer = Buffer.from(str.toString(), 'binary');
85 | }
86 | return buffer.toString('base64');
87 | }
88 |
89 | /**
90 | * Detect the file name for the given data source.
91 | *
92 | * @ignore
93 | * @static
94 | * @param {string} source - A path to a file as string.
95 | * @returns {string}
96 | */
97 | static fileNameForUpload(source) {
98 | return path.basename(source);
99 | }
100 |
101 | /**
102 | * Get the data from the source that should be uploaded.
103 | *
104 | * @ignore
105 | * @static
106 | * @param {string} source - A path to a file as string.
107 | * @returns {Stream.Readable}
108 | */
109 | static dataForUpload(source) {
110 | return fs.createReadStream(source);
111 | }
112 |
113 | /**
114 | * Downloads files to local storage and returns a list of file paths.
115 | *
116 | * @static
117 | * @param {Connection} con
118 | * @param {Array.>} assets
119 | * @param {string} targetFolder
120 | * @returns {Promise>}
121 | * @throws {Error}
122 | */
123 | static async downloadResults(con, assets, targetFolder) {
124 | let files = [];
125 | const promises = assets.map(async (link) => {
126 | let parsedUrl = url.parse(link.href);
127 | let targetPath = path.join(targetFolder, path.basename(parsedUrl.pathname));
128 | let data = await con.download(link.href, false);
129 | if (data instanceof Stream.Readable) {
130 | await Environment.saveToFile(data, targetPath);
131 | files.push(targetPath);
132 | }
133 | else {
134 | throw new Error("Data retrieved is not a Stream");
135 | }
136 | });
137 |
138 | await Promise.all(promises);
139 | return files;
140 | }
141 |
142 | /**
143 | * Streams data into a file.
144 | *
145 | * @static
146 | * @async
147 | * @param {Stream.Readable} data - Data stream to read from.
148 | * @param {string} filename - File path to store the data at.
149 | * @returns {Promise}
150 | * @throws {Error}
151 | */
152 | static saveToFile(data, filename) {
153 | return new Promise((resolve, reject) => {
154 | let writeStream = fs.createWriteStream(filename);
155 | writeStream.on('close', (err) => {
156 | if (err) {
157 | return reject(err);
158 | }
159 | resolve();
160 | });
161 | data.pipe(writeStream);
162 | });
163 | }
164 | }
165 |
166 | Environment.url = '';
167 |
168 | module.exports = Environment;
169 |
--------------------------------------------------------------------------------
/src/oidcprovider.js:
--------------------------------------------------------------------------------
1 | const Utils = require('@openeo/js-commons/src/utils');
2 | const AuthProvider = require('./authprovider');
3 | const Environment = require('./env');
4 | const Oidc = require('oidc-client');
5 |
6 | /**
7 | * The Authentication Provider for OpenID Connect.
8 | *
9 | * See the openid-connect-popup.html and openid-connect-redirect.html files in
10 | * the `/examples/oidc` folder for usage examples in the browser.
11 | *
12 | * If you want to implement OIDC in a non-browser environment, you can override
13 | * the OidcProvider or AuthProvider classes with custom behavior.
14 | * In this case you must provide a function that creates your new class to the
15 | * `Connection.setOidcProviderFactory()` method.
16 | *
17 | * @augments AuthProvider
18 | * @see Connection#setOidcProviderFactory
19 | */
20 | class OidcProvider extends AuthProvider {
21 |
22 | /**
23 | * Checks whether the required OIDC client library `openid-client-js` is available.
24 | *
25 | * @static
26 | * @returns {boolean}
27 | */
28 | static isSupported() {
29 | return Utils.isObject(Oidc) && Boolean(Oidc.UserManager);
30 | }
31 |
32 | /**
33 | * Finishes the OpenID Connect sign in (authentication) workflow.
34 | *
35 | * Must be called in the page that OpenID Connect redirects to after logging in.
36 | *
37 | * Supported only in Browser environments.
38 | *
39 | * @async
40 | * @static
41 | * @param {OidcProvider} provider - A OIDC provider to assign the user to.
42 | * @param {object.} [options={}] - Object with additional options.
43 | * @returns {Promise} For uiMethod = 'redirect' only: OIDC User
44 | * @throws {Error}
45 | * @see https://github.com/IdentityModel/oidc-client-js/wiki#other-optional-settings
46 | */
47 | static async signinCallback(provider = null, options = {}) {
48 | let url = Environment.getUrl();
49 | if (!provider) {
50 | // No provider options available, try to detect response mode from URL
51 | provider = new OidcProvider(null, {});
52 | provider.setGrant(url.includes('?') ? 'authorization_code+pkce' : 'implicit');
53 | }
54 | let providerOptions = provider.getOptions(options);
55 | let oidc = new Oidc.UserManager(providerOptions);
56 | return await oidc.signinCallback(url);
57 | }
58 |
59 | /**
60 | * Creates a new OidcProvider instance to authenticate using OpenID Connect.
61 | *
62 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
63 | * @param {OidcProviderMeta} options - OpenID Connect Provider details as returned by the API.
64 | */
65 | constructor(connection, options) {
66 | super("oidc", connection, options);
67 |
68 | this.manager = null;
69 | this.listeners = {};
70 |
71 | /**
72 | * The authenticated OIDC user.
73 | *
74 | * @type {Oidc.User}
75 | */
76 | this.user = null;
77 |
78 | /**
79 | * The client ID to use for authentication.
80 | *
81 | * @type {string | null}
82 | */
83 | this.clientId = null;
84 |
85 | /**
86 | * The grant type (flow) to use for this provider.
87 | *
88 | * Either "authorization_code+pkce" (default) or "implicit"
89 | *
90 | * @type {string}
91 | */
92 | this.grant = "authorization_code+pkce"; // Set this before calling detectDefaultClient
93 |
94 | /**
95 | * The issuer, i.e. the link to the identity provider.
96 | *
97 | * @type {string}
98 | */
99 | this.issuer = options.issuer || "";
100 |
101 | /**
102 | * The scopes to be requested.
103 | *
104 | * @type {Array.}
105 | */
106 | this.scopes = Array.isArray(options.scopes) && options.scopes.length > 0 ? options.scopes : ['openid'];
107 |
108 | /**
109 | * The scope that is used to request a refresh token.
110 | *
111 | * @type {string}
112 | */
113 | this.refreshTokenScope = "offline_access";
114 |
115 | /**
116 | * Any additional links.
117 | *
118 | *
119 | * @type {Array. }
120 | */
121 | this.links = Array.isArray(options.links) ? options.links : [];
122 |
123 | /**
124 | * The default clients made available by the back-end.
125 | *
126 | * @type {Array.}
127 | */
128 | this.defaultClients = Array.isArray(options.default_clients) ? options.default_clients : [];
129 |
130 | /**
131 | * The detected default Client.
132 | *
133 | * @type {OidcClient}
134 | */
135 | this.defaultClient = this.detectDefaultClient();
136 | }
137 |
138 | /**
139 | * Adds a listener to one of the following events:
140 | *
141 | * - AccessTokenExpiring: Raised prior to the access token expiring.
142 | * - AccessTokenExpired: Raised after the access token has expired.
143 | * - SilentRenewError: Raised when the automatic silent renew has failed.
144 | *
145 | * @param {string} event
146 | * @param {Function} callback
147 | * @param {string} [scope="default"]
148 | */
149 | addListener(event, callback, scope = 'default') {
150 | this.manager.events[`add${event}`](callback);
151 | this.listeners[`${scope}:${event}`] = callback;
152 | }
153 |
154 | /**
155 | * Removes the listener for the given event that has been set with addListener.
156 | *
157 | * @param {string} event
158 | * @param {string} [scope="default"]
159 | * @see OidcProvider#addListener
160 | */
161 | removeListener(event, scope = 'default') {
162 | this.manager.events[`remove${event}`](this.listeners[event]);
163 | delete this.listeners[`${scope}:${event}`];
164 | }
165 |
166 | /**
167 | * Authenticate with OpenID Connect (OIDC).
168 | *
169 | * Supported only in Browser environments.
170 | *
171 | * @async
172 | * @param {object.} [options={}] - Object with authentication options.
173 | * @param {boolean} [requestRefreshToken=false] - If set to `true`, adds a scope to request a refresh token.
174 | * @returns {Promise}
175 | * @throws {Error}
176 | * @see https://github.com/IdentityModel/oidc-client-js/wiki#other-optional-settings
177 | * @see {OidcProvider#refreshTokenScope}
178 | */
179 | async login(options = {}, requestRefreshToken = false) {
180 | if (!this.issuer || typeof this.issuer !== 'string') {
181 | throw new Error("No Issuer URL available for OpenID Connect");
182 | }
183 |
184 | this.manager = new Oidc.UserManager(this.getOptions(options, requestRefreshToken));
185 | this.addListener('UserLoaded', async () => this.setUser(await this.manager.getUser()), 'js-client');
186 | this.addListener('AccessTokenExpired', () => this.setUser(null), 'js-client');
187 | if (OidcProvider.uiMethod === 'popup') {
188 | await this.manager.signinPopup();
189 | }
190 | else {
191 | await this.manager.signinRedirect();
192 | }
193 | }
194 |
195 | /**
196 | * Logout from the established session.
197 | *
198 | * @async
199 | */
200 | async logout() {
201 | if (this.manager !== null) {
202 | try {
203 | if (OidcProvider.uiMethod === 'popup') {
204 | await this.manager.signoutPopup();
205 | }
206 | else {
207 | await this.manager.signoutRedirect({
208 | post_logout_redirect_uri: Environment.getUrl()
209 | });
210 | }
211 | } catch (error) {
212 | console.warn(error);
213 | }
214 | super.logout();
215 | this.removeListener('UserLoaded', 'js-client');
216 | this.removeListener('AccessTokenExpired', 'js-client');
217 | this.manager = null;
218 | this.setUser(null);
219 | }
220 | }
221 |
222 | /**
223 | * Returns the options for the OIDC client library.
224 | *
225 | * Options can be overridden by custom options via the options parameter.
226 | *
227 | * @protected
228 | * @param {object.} options
229 | * @param {boolean} [requestRefreshToken=false] - If set to `true`, adds a scope to request a refresh token.
230 | * @returns {object.}
231 | * @see {OidcProvider#refreshTokenScope}
232 | */
233 | getOptions(options = {}, requestRefreshToken = false) {
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 |
240 | return Object.assign({
241 | client_id: this.clientId,
242 | redirect_uri: OidcProvider.redirectUrl,
243 | authority: this.issuer.replace('/.well-known/openid-configuration', ''),
244 | scope: scope.join(' '),
245 | validateSubOnSilentRenew: true,
246 | response_type,
247 | response_mode: response_type.includes('code') ? 'query' : 'fragment'
248 | }, options);
249 | }
250 |
251 | /**
252 | * Get the response_type based on the grant type.
253 | *
254 | * @protected
255 | * @returns {string}
256 | * @throws {Error}
257 | */
258 | getResponseType() {
259 | switch(this.grant) {
260 | case 'authorization_code+pkce':
261 | return 'code';
262 | case 'implicit':
263 | return 'token id_token';
264 | default:
265 | throw new Error('Grant Type not supported');
266 | }
267 | }
268 |
269 | /**
270 | * Sets the grant type (flow) used for OIDC authentication.
271 | *
272 | * @param {string} grant - Grant Type
273 | * @throws {Error}
274 | */
275 | setGrant(grant) { //
276 | switch(grant) {
277 | case 'authorization_code+pkce':
278 | case 'implicit':
279 | this.grant = grant;
280 | break;
281 | default:
282 | throw new Error('Grant Type not supported');
283 | }
284 | }
285 |
286 | /**
287 | * Sets the Client ID for OIDC authentication.
288 | *
289 | * This may override a detected default client ID.
290 | *
291 | * @param {string | null} clientId
292 | */
293 | setClientId(clientId) {
294 | this.clientId = clientId;
295 | }
296 |
297 | /**
298 | * Sets the OIDC User.
299 | *
300 | * @see https://github.com/IdentityModel/oidc-client-js/wiki#user
301 | * @param {Oidc.User | null} user - The OIDC User. Passing `null` resets OIDC authentication details.
302 | */
303 | setUser(user) {
304 | if (!user) {
305 | this.user = null;
306 | this.setToken(null);
307 | }
308 | else {
309 | this.user = user;
310 | this.setToken(user.access_token);
311 | }
312 | }
313 |
314 | /**
315 | * Returns a display name for the authenticated user.
316 | *
317 | * @returns {string?} Name of the user or `null`
318 | */
319 | getDisplayName() {
320 | if (this.user && Utils.isObject(this.user.profile)) {
321 | return this.user.profile.name || this.user.profile.preferred_username || this.user.profile.email || null;
322 | }
323 | return null;
324 | }
325 |
326 | /**
327 | * Detects the default OIDC client ID for the given redirect URL.
328 | *
329 | * Sets the grant and client ID accordingly.
330 | *
331 | * @returns {OidcClient | null}
332 | * @see OidcProvider#setGrant
333 | * @see OidcProvider#setClientId
334 | */
335 | detectDefaultClient() {
336 | for(let grant of OidcProvider.grants) {
337 | let defaultClient = this.defaultClients.find(client => Boolean(client.grant_types.includes(grant) && Array.isArray(client.redirect_urls) && client.redirect_urls.find(url => url.startsWith(OidcProvider.redirectUrl))));
338 | if (defaultClient) {
339 | this.setGrant(grant);
340 | this.setClientId(defaultClient.id);
341 | this.defaultClient = defaultClient;
342 | return defaultClient;
343 | }
344 | }
345 |
346 | return null;
347 | }
348 |
349 | }
350 |
351 | /**
352 | * The global "UI" method to use to open the login URL, either "redirect" (default) or "popup".
353 | *
354 | * @type {string}
355 | */
356 | OidcProvider.uiMethod = 'redirect';
357 |
358 | /**
359 | * The global redirect URL to use.
360 | *
361 | * By default uses the location of the browser, but removes fragment, query and
362 | * trailing slash.
363 | * The fragment conflicts with the fragment appended by the Implicit Flow and
364 | * the query conflicts with the query appended by the Authorization Code Flow.
365 | * The trailing slash is removed for consistency.
366 | *
367 | * @type {string}
368 | */
369 | OidcProvider.redirectUrl = Environment.getUrl().split('#')[0].split('?')[0].replace(/\/$/, '');
370 |
371 | /**
372 | * The supported OpenID Connect grants (flows).
373 | *
374 | * The grants are given as defined in openEO API, e.g. `implicit` and/or `authorization_code+pkce`
375 | * If not defined there, consult the OpenID Connect Discovery documentation.
376 | *
377 | * Lists the grants by priority so that the first grant is the default grant.
378 | * The default grant type since client version 2.0.0 is 'authorization_code+pkce'.
379 | *
380 | * @type {Array.}
381 | */
382 | OidcProvider.grants = [
383 | 'authorization_code+pkce',
384 | 'implicit'
385 | ];
386 |
387 | module.exports = OidcProvider;
388 |
--------------------------------------------------------------------------------
/src/openeo.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const Utils = require('@openeo/js-commons/src/utils');
3 | const Versions = require('@openeo/js-commons/src/versions');
4 |
5 | // API wrapper
6 | const Connection = require('./connection');
7 | const Job = require('./job');
8 | const Logs = require('./logs');
9 | const UserFile = require('./userfile');
10 | const UserProcess = require('./userprocess');
11 | const Service = require('./service');
12 |
13 | // Auth Providers
14 | const AuthProvider = require('./authprovider');
15 | const BasicProvider = require('./basicprovider');
16 | const OidcProvider = require('./oidcprovider');
17 |
18 | // Response wrapper
19 | const Capabilities = require('./capabilities');
20 | const FileTypes = require('./filetypes');
21 |
22 | // Builder
23 | const Builder = require('./builder/builder');
24 | const BuilderNode = require('./builder/node');
25 | const Parameter = require('./builder/parameter');
26 | const Formula = require('./builder/formula');
27 |
28 | const MIN_API_VERSION = '1.0.0-rc.2';
29 | const MAX_API_VERSION = '1.x.x';
30 |
31 | /**
32 | * Main class to start with openEO. Allows to connect to a server.
33 | *
34 | * @hideconstructor
35 | */
36 | class OpenEO {
37 |
38 | /**
39 | * Connect to a back-end with version discovery (recommended).
40 | *
41 | * Includes version discovery (request to `GET /well-known/openeo`) and connects to the most suitable version compatible to this JS client version.
42 | * Requests the capabilities and authenticates where required.
43 | *
44 | * @async
45 | * @param {string} url - The server URL to connect to.
46 | * @param {Options} [options={}] - Additional options for the connection.
47 | * @returns {Promise}
48 | * @throws {Error}
49 | * @static
50 | */
51 | static async connect(url, options = {}) {
52 | let wellKnownUrl = Utils.normalizeUrl(url, '/.well-known/openeo');
53 | let versionedUrl = url;
54 | let response = null;
55 | try {
56 | response = await axios.get(wellKnownUrl, {timeout: 5000});
57 |
58 | if (!Utils.isObject(response.data) || !Array.isArray(response.data.versions)) {
59 | throw new Error("Well-Known Document doesn't list any versions.");
60 | }
61 | } catch(error) {
62 | console.warn("Can't read well-known document, connecting directly to the specified URL as fallback mechanism. Reason: " + error.message);
63 | }
64 |
65 | if (Utils.isObject(response)) {
66 | let version = Versions.findLatest(response.data.versions, true, MIN_API_VERSION, MAX_API_VERSION);
67 | if (version !== null) {
68 | versionedUrl = version.url;
69 | }
70 | else {
71 | throw new Error("Server not supported. Client only supports the API versions between " + MIN_API_VERSION + " and " + MAX_API_VERSION);
72 | }
73 | }
74 |
75 | let connection = await OpenEO.connectDirect(versionedUrl, options);
76 | connection.url = url;
77 | return connection;
78 | }
79 |
80 | /**
81 | * Connects directly to a back-end instance, without version discovery (NOT recommended).
82 | *
83 | * Doesn't do version discovery, therefore a URL of a versioned API must be specified. Requests the capabilities and authenticates where required.
84 | *
85 | * @async
86 | * @param {string} versionedUrl - The server URL to connect to.
87 | * @param {Options} [options={}] - Additional options for the connection.
88 | * @returns {Promise}
89 | * @throws {Error}
90 | * @static
91 | */
92 | static async connectDirect(versionedUrl, options = {}) {
93 | let connection = new Connection(versionedUrl, options);
94 |
95 | // Check whether back-end is accessible and supports a compatible version.
96 | let capabilities = await connection.init();
97 | if (Versions.compare(capabilities.apiVersion(), MIN_API_VERSION, "<") || Versions.compare(capabilities.apiVersion(), MAX_API_VERSION, ">")) {
98 | throw new Error("Client only supports the API versions between " + MIN_API_VERSION + " and " + MAX_API_VERSION);
99 | }
100 |
101 | return connection;
102 | }
103 |
104 | /**
105 | * Returns the version number of the client.
106 | *
107 | * Not to confuse with the API version(s) supported by the client.
108 | *
109 | * @returns {string} Version number (according to SemVer).
110 | */
111 | static clientVersion() {
112 | return "2.8.0";
113 | }
114 |
115 | }
116 |
117 | OpenEO.Environment = require('./env');
118 |
119 | module.exports = {
120 | AbortController,
121 | AuthProvider,
122 | BasicProvider,
123 | Capabilities,
124 | Connection,
125 | FileTypes,
126 | Job,
127 | Logs,
128 | OidcProvider,
129 | OpenEO,
130 | Service,
131 | UserFile,
132 | UserProcess,
133 | Builder,
134 | BuilderNode,
135 | Parameter,
136 | Formula
137 | };
138 |
--------------------------------------------------------------------------------
/src/pages.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file */
2 |
3 | const Job = require('./job.js');
4 | const Service = require('./service.js');
5 | const UserFile = require('./userfile.js');
6 | const UserProcess = require('./userprocess.js');
7 | const Utils = require('@openeo/js-commons/src/utils');
8 | const StacMigrate = require('@radiantearth/stac-migrate');
9 |
10 | const FED_MISSING = 'federation:missing';
11 |
12 | /**
13 | * A class to handle pagination of resources.
14 | *
15 | * @abstract
16 | */
17 | class Pages {
18 | /**
19 | * Creates an instance of Pages.
20 | *
21 | * @param {Connection} connection
22 | * @param {string} endpoint
23 | * @param {string} key
24 | * @param {Constructor} cls - Class
25 | * @param {object} [params={}]
26 | * @param {string} primaryKey
27 | */
28 | constructor(connection, endpoint, key, cls, params = {}, primaryKey = "id") {
29 | this.connection = connection;
30 | this.nextUrl = endpoint;
31 | this.key = key;
32 | this.primaryKey = primaryKey;
33 | this.cls = cls;
34 | if (!(params.limit > 0)) {
35 | delete params.limit;
36 | }
37 | this.params = params;
38 | }
39 |
40 | /**
41 | * Returns true if there are more pages to fetch.
42 | *
43 | * @returns {boolean}
44 | */
45 | hasNextPage() {
46 | return this.nextUrl !== null;
47 | }
48 |
49 | /**
50 | * Returns the next page of resources.
51 | *
52 | * @async
53 | * @param {Array.} oldObjects - Existing objects to update, if any.
54 | * @param {boolean} [toArray=true] - Whether to return the objects as a simplified array or as an object with all information.
55 | * @returns {Array.}
56 | * @throws {Error}
57 | */
58 | async nextPage(oldObjects = [], toArray = true) {
59 | // Request data from server
60 | const response = await this.connection._get(this.nextUrl, this.params);
61 |
62 | let data = response.data;
63 | // Check response
64 | if (!Utils.isObject(data)) {
65 | throw new Error(`Response is invalid, is not an object`);
66 | }
67 | if (!Array.isArray(data[this.key])) {
68 | throw new Error(`Response is invalid, '${this.key}' property is not an array`);
69 | }
70 |
71 | // Update existing objects if needed
72 | let newObjects = data[this.key].map(updated => {
73 | let resource = oldObjects.find(old => old[this.primaryKey] === updated[this.primaryKey]);
74 | if (resource) {
75 | resource.setAll(updated);
76 | }
77 | else {
78 | resource = this._createObject(updated);
79 | }
80 | return resource;
81 | });
82 |
83 | // Store objects in cache if needed
84 | newObjects = this._cache(newObjects);
85 |
86 | // Add self link if missing
87 | data.links = this._ensureArray(data.links);
88 | const selfLink = this.connection._getLinkHref(data.links, 'self');
89 | if (!selfLink) {
90 | data.links.push({rel: 'self', href: this.nextUrl});
91 | }
92 |
93 | // Check whether a next page is available
94 | this.nextUrl = this._getNextLink(response);
95 | // Don't append initial params to the next URL
96 | this.params = null;
97 |
98 | // Either return as ResponseArray or full API response body
99 | if (toArray) {
100 | newObjects.links = data.links;
101 | newObjects[FED_MISSING] = this._ensureArray(data[FED_MISSING]);
102 | return newObjects;
103 | }
104 | else {
105 | data[this.key] = newObjects;
106 | return data;
107 | }
108 | }
109 |
110 | /**
111 | * Ensures a variable is an array.
112 | *
113 | * @protected
114 | * @param {*} x
115 | * @returns {Array}
116 | */
117 | _ensureArray(x) {
118 | return Array.isArray(x) ? x : [];
119 | }
120 |
121 | /**
122 | * Creates a facade for the object, if needed.
123 | *
124 | * @protected
125 | * @param {object} obj
126 | * @returns {object}
127 | */
128 | _createObject(obj) {
129 | if (this.cls) {
130 | const cls = this.cls;
131 | const newObj = new cls(this.connection, obj[this.primaryKey]);
132 | newObj.setAll(obj);
133 | return newObj;
134 | }
135 | else {
136 | return obj;
137 | }
138 | }
139 |
140 | /**
141 | * Caches the plain objects if needed.
142 | *
143 | * @param {Array.} objects
144 | * @returns {Array.}
145 | */
146 | _cache(objects) {
147 | return objects;
148 | }
149 |
150 | /**
151 | * Get the URL of the next page from a response.
152 | *
153 | * @protected
154 | * @param {AxiosResponse} response
155 | * @returns {string | null}
156 | */
157 | _getNextLink(response) {
158 | const links = this.connection.makeLinksAbsolute(response.data.links, response);
159 | return this.connection._getLinkHref(links, 'next');
160 | }
161 |
162 | /**
163 | * Makes this class asynchronously iterable.
164 | *
165 | * @returns {AsyncIterator}
166 | */
167 | [Symbol.asyncIterator]() {
168 | return {
169 | self: this,
170 | /**
171 | * Get the next page of resources.
172 | *
173 | * @async
174 | * @returns {{done: boolean, value: Array.}}
175 | */
176 | async next() {
177 | const done = !this.self.hasNextPage();
178 | let value;
179 | if (!done) {
180 | value = await this.self.nextPage();
181 | }
182 | return { done, value };
183 | }
184 | }
185 | }
186 |
187 | }
188 |
189 | /**
190 | * Paginate through jobs.
191 | */
192 | class JobPages extends Pages {
193 | /**
194 | * Paginate through jobs.
195 | *
196 | * @param {Connection} connection
197 | * @param {?number} limit
198 | */
199 | constructor(connection, limit = null) {
200 | super(connection, "/jobs", "jobs", Job, {limit});
201 | }
202 | }
203 |
204 | /**
205 | * Paginate through services.
206 | */
207 | class ServicePages extends Pages {
208 | /**
209 | * Paginate through services.
210 | *
211 | * @param {Connection} connection
212 | * @param {?number} limit
213 | */
214 | constructor(connection, limit = null) {
215 | super(connection, "/services", "services", Service, {limit});
216 | }
217 | }
218 |
219 | /**
220 | * Paginate through user files.
221 | */
222 | class UserFilePages extends Pages {
223 | /**
224 | * Paginate through user files.
225 | *
226 | * @param {Connection} connection
227 | * @param {?number} limit
228 | */
229 | constructor(connection, limit = null) {
230 | super(connection, "/files", "files", UserFile, {limit}, "path");
231 | }
232 | }
233 |
234 | /**
235 | * Paginate through processes.
236 | */
237 | class ProcessPages extends Pages {
238 | /**
239 | * Paginate through processes.
240 | *
241 | * @param {Connection} connection
242 | * @param {?number} limit
243 | * @param {?string} namespace
244 | */
245 | constructor(connection, limit = null, namespace = null) {
246 | if (!namespace) {
247 | namespace = 'backend';
248 | }
249 | let endpoint;
250 | let cls = null
251 | if (namespace === 'user') {
252 | endpoint = '/process_graphs';
253 | cls = UserProcess;
254 | }
255 | else {
256 | endpoint = '/processes';
257 | if (namespace !== 'backend') {
258 | const normalized = connection.normalizeNamespace(namespace);
259 | endpoint += `/${normalized}`;
260 | }
261 | }
262 | super(connection, endpoint, "processes", cls, {limit});
263 | this.namespace = namespace;
264 | }
265 |
266 | /**
267 | * Caches the objects to the ProcessRegistry.
268 | *
269 | * @param {Array.} objects
270 | * @returns {Array.}
271 | */
272 | _cache(objects) {
273 | const plainObjects = objects.map(p => (typeof p.toJSON === 'function' ? p.toJSON() : p));
274 | this.connection.processes.addAll(plainObjects, this.namespace);
275 | if (!this.cls) {
276 | for (let i in objects) {
277 | objects[i] = this.connection.processes.get(objects[i].id, this.namespace);
278 | }
279 | }
280 | return objects;
281 | }
282 | }
283 |
284 | /**
285 | * Paginate through collections.
286 | */
287 | class CollectionPages extends Pages {
288 | /**
289 | * Paginate through collections.
290 | *
291 | * @param {Connection} connection
292 | * @param {?number} limit
293 | */
294 | constructor(connection, limit = null) {
295 | super(connection, "/collections", "collections", null, {limit});
296 | }
297 |
298 | /**
299 | * Migrates the STAC collection to the latest version.
300 | *
301 | * @param {object} obj
302 | * @returns {Collection}
303 | */
304 | _createObject(obj) {
305 | if (obj.stac_version) {
306 | return StacMigrate.collection(obj);
307 | }
308 | return obj;
309 | }
310 | }
311 |
312 | /**
313 | * Paginate through collection items.
314 | */
315 | class ItemPages extends Pages {
316 | /**
317 | * Paginate through collection items.
318 | *
319 | * @param {Connection} connection
320 | * @param {string} collectionId
321 | * @param {object} params
322 | */
323 | constructor(connection, collectionId, params) {
324 | super(connection, `/collections/${collectionId}/items`, "features", null, params);
325 | }
326 |
327 | /**
328 | * Migrates the STAC item to the latest version.
329 | *
330 | * @param {object} obj
331 | * @returns {Item}
332 | */
333 | _createObject(obj) {
334 | if (obj.stac_version) {
335 | return StacMigrate.item(obj);
336 | }
337 | return obj;
338 | }
339 | }
340 |
341 | module.exports = {
342 | Pages,
343 | CollectionPages,
344 | ItemPages,
345 | JobPages,
346 | ProcessPages,
347 | ServicePages,
348 | UserFilePages
349 | }
350 |
--------------------------------------------------------------------------------
/src/service.js:
--------------------------------------------------------------------------------
1 | const BaseEntity = require('./baseentity');
2 | const Logs = require('./logs');
3 |
4 | /**
5 | * A Secondary Web Service.
6 | *
7 | * @augments BaseEntity
8 | */
9 | class Service extends BaseEntity {
10 |
11 | /**
12 | * Creates an object representing a secondary web service stored at the back-end.
13 | *
14 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
15 | * @param {string} serviceId - The service ID.
16 | */
17 | constructor(connection, serviceId) {
18 | super(connection, ["id", "title", "description", "process", "url", "type", "enabled", "configuration", "attributes", "created", "plan", "costs", "budget", "usage", ["log_level", "logLevel"], "links"]);
19 | /**
20 | * The identifier of the service.
21 | * @public
22 | * @readonly
23 | * @type {string}
24 | */
25 | this.id = serviceId;
26 | /**
27 | * @public
28 | * @readonly
29 | * @type {?string}
30 | */
31 | this.title = undefined;
32 | /**
33 | * @public
34 | * @readonly
35 | * @type {?string}
36 | */
37 | this.description = undefined;
38 | /**
39 | * The process chain to be executed.
40 | * @public
41 | * @readonly
42 | * @type {?Process}
43 | */
44 | this.process = undefined;
45 | /**
46 | * URL at which the secondary web service is accessible
47 | * @public
48 | * @readonly
49 | * @type {?string}
50 | */
51 | this.url = undefined;
52 | /**
53 | * Web service type (protocol / standard) that is exposed.
54 | * @public
55 | * @readonly
56 | * @type {?string}
57 | */
58 | this.type = undefined;
59 | /**
60 | * @public
61 | * @readonly
62 | * @type {?boolean}
63 | */
64 | this.enabled = undefined;
65 | /**
66 | * Map of configuration settings, i.e. the setting names supported by the secondary web service combined with actual values.
67 | * @public
68 | * @readonly
69 | * @type {?object.}
70 | */
71 | this.configuration = undefined;
72 | /**
73 | * Additional attributes of the secondary web service, e.g. available layers for a WMS based on the bands in the underlying GeoTiff.
74 | * @public
75 | * @readonly
76 | * @type {?object.}
77 | */
78 | this.attributes = undefined;
79 | /**
80 | * Date and time of creation, formatted as a RFC 3339 date-time.
81 | * @public
82 | * @readonly
83 | * @type {?string}
84 | */
85 | this.created = undefined;
86 | /**
87 | * The billing plan to process and charge the service with.
88 | * @public
89 | * @readonly
90 | * @type {?string}
91 | */
92 | this.plan = undefined;
93 | /**
94 | * An amount of money or credits in the currency specified by the back-end.
95 | * @public
96 | * @readonly
97 | * @type {?number}
98 | */
99 | this.costs = undefined;
100 | /**
101 | * Maximum amount of costs the request is allowed to produce in the currency specified by the back-end.
102 | * @public
103 | * @readonly
104 | * @type {?number}
105 | */
106 | this.budget = undefined;
107 | }
108 |
109 | /**
110 | * Updates the data stored in this object by requesting the secondary web service metadata from the back-end.
111 | *
112 | * @async
113 | * @returns {Promise} The updates service object (this).
114 | * @throws {Error}
115 | */
116 | async describeService() {
117 | let response = await this.connection._get('/services/' + this.id);
118 | return this.setAll(response.data);
119 | }
120 |
121 | /**
122 | * Modifies the secondary web service at the back-end and afterwards updates this object, too.
123 | *
124 | * @async
125 | * @param {object} parameters - An object with properties to update, each of them is optional, but at least one of them must be specified. Additional properties can be set if the server supports them.
126 | * @param {Process} parameters.process - A new process.
127 | * @param {string} parameters.title - A new title.
128 | * @param {string} parameters.description - A new description.
129 | * @param {boolean} parameters.enabled - Enables (`true`) or disables (`false`) the service.
130 | * @param {object.} parameters.configuration - A new set of configuration parameters to set for the service.
131 | * @param {string} parameters.plan - A new plan.
132 | * @param {number} parameters.budget - A new budget.
133 | * @returns {Promise} The updated service object (this).
134 | * @throws {Error}
135 | */
136 | async updateService(parameters) {
137 | await this.connection._patch('/services/' + this.id, this._convertToRequest(parameters));
138 | if (this._supports('describeService')) {
139 | return await this.describeService();
140 | }
141 | else {
142 | return this.setAll(parameters);
143 | }
144 | }
145 |
146 | /**
147 | * Deletes the secondary web service from the back-end.
148 | *
149 | * @async
150 | * @throws {Error}
151 | */
152 | async deleteService() {
153 | await this.connection._delete('/services/' + this.id);
154 | }
155 |
156 | /**
157 | * Get logs for the secondary web service from the back-end.
158 | *
159 | * @param {?string} [level=null] - Minimum level of logs to return.
160 | * @returns {Logs}
161 | */
162 | debugService(level = null) {
163 | return new Logs(this.connection, '/services/' + this.id + '/logs', level);
164 | }
165 |
166 | /**
167 | * Checks for new log entries every x seconds.
168 | *
169 | * On every status change (enabled/disabled) observed or on new log entries
170 | * (if supported by the back-end and not disabled via `requestLogs`), the
171 | * callback is executed. It may also be executed once at the beginning.
172 | * The callback receives the updated service (this object) and the logs (array) passed.
173 | *
174 | * Returns a function that can be called to stop monitoring the service manually.
175 | * The monitoring must be stopped manually, otherwise it runs forever.
176 | *
177 | * This is only supported if describeService is supported by the back-end.
178 | *
179 | * @param {Function} callback
180 | * @param {number} [interval=60] - Interval between update requests, in seconds as integer.
181 | * @param {boolean} [requestLogs=true] - Enables/Disables requesting logs
182 | * @returns {Function}
183 | * @throws {Error}
184 | */
185 | monitorService(callback, interval = 60, requestLogs = true) {
186 | if (typeof callback !== 'function' || interval < 1) {
187 | return;
188 | }
189 | let capabilities = this.connection.capabilities();
190 | if (!capabilities.hasFeature('describeService')) {
191 | throw new Error('Monitoring Services not supported by the back-end.');
192 | }
193 |
194 | let wasEnabled = this.enabled;
195 | let intervalId = null;
196 | let logIterator = null;
197 | if (capabilities.hasFeature('debugService') && requestLogs) {
198 | logIterator = this.debugService();
199 | }
200 | let monitorFn = async () => {
201 | if (this.getDataAge() > 1) {
202 | await this.describeService();
203 | }
204 | let logs = logIterator ? await logIterator.nextLogs() : [];
205 | if (wasEnabled !== this.enabled || logs.length > 0) {
206 | callback(this, logs);
207 | }
208 | wasEnabled = this.enabled;
209 | };
210 | setTimeout(monitorFn, 0);
211 | intervalId = setInterval(monitorFn, interval * 1000);
212 | let stopFn = () => {
213 | if (intervalId) {
214 | clearInterval(intervalId);
215 | intervalId = null;
216 | }
217 | };
218 | return stopFn;
219 | }
220 | }
221 |
222 | module.exports = Service;
223 |
--------------------------------------------------------------------------------
/src/typedefs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An error.
3 | *
4 | * @typedef ApiError
5 | * @type {object}
6 | * @property {string} id
7 | * @property {string} code
8 | * @property {string} message
9 | * @property {Array. } links
10 | */
11 |
12 | /**
13 | * Authentication Provider details.
14 | *
15 | * @typedef AuthProviderMeta
16 | * @type {object}
17 | * @property {?string} id Provider identifier, may not be used for all authentication methods.
18 | * @property {string} title Title for the authentication method.
19 | * @property {string} description Description for the authentication method.
20 | */
21 |
22 | /**
23 | * Response for a HTTP request.
24 | *
25 | * @typedef AxiosResponse
26 | * @type {object}
27 | * @property {*} data
28 | * @property {number} status
29 | * @property {string} statusText
30 | * @property {*} headers
31 | * @property {object.} config
32 | * @property {*} request
33 | */
34 |
35 | /**
36 | * @typedef BillingPlan
37 | * @type {object}
38 | * @property {string} name Name of the billing plan.
39 | * @property {string} description A description of the billing plan, may include CommonMark syntax.
40 | * @property {boolean} paid `true` if it is a paid plan, otherwise `false`.
41 | * @property {string} url A URL pointing to a page describing the billing plan.
42 | * @property {boolean} default `true` if it is the default plan of the back-end, otherwise `false`.
43 | */
44 |
45 | /**
46 | * @typedef Collections
47 | * @type {object}
48 | * @property {Array.} collections
49 | * @property {Array. } links
50 | * @property {Array.} ["federation:missing"] A list of backends from the federation that are missing in the response data.
51 | */
52 |
53 | /**
54 | * @typedef Collection
55 | * @type {object.}
56 | */
57 |
58 | /**
59 | * @typedef FileTypesAPI
60 | * @type {object}
61 | * @property {object.} input - File types supported to import
62 | * @property {object.} output - File types supported to export
63 | */
64 |
65 | /**
66 | * @typedef FileType
67 | * @type {object}
68 | * @property {string} title
69 | * @property {string} description
70 | * @property {Array.} gis_data_types
71 | * @property {object.} parameters
72 | * @property {Array. } links
73 | */
74 |
75 | /**
76 | * Reference to a parameter.
77 | *
78 | * @typedef FromNode
79 | * @type {object}
80 | * @property {string} from_node - The node identifier.
81 | */
82 |
83 | /**
84 | * Reference to a parameter.
85 | *
86 | * @typedef FromParameter
87 | * @type {object}
88 | * @property {string} from_parameter - The name of the parameter.
89 | */
90 |
91 | /**
92 | * @typedef Item
93 | * @type {object.}
94 | */
95 |
96 | /**
97 | * @typedef ItemCollection
98 | * @type {object}
99 | * @property {Array.- } features - The items in the collection.
100 | * @property {?Array.
} links - Additional links, e.g. for pagination.
101 | * @property {?string} timeStamp This property indicates the time and date when the response was generated.
102 | * @property {?number} numberMatched The number (integer) of features of the feature type that match the selection parameters.
103 | * @property {?number} numberReturned The number (integer) of features in the feature collection.
104 | */
105 |
106 | /**
107 | * @typedef JobEstimate
108 | * @type {object}
109 | * @property {?number} costs
110 | * @property {string} duration
111 | * @property {number} size in bytes as integer
112 | * @property {?number} downloads_included integer
113 | * @property {string} expires
114 | */
115 |
116 | /**
117 | * A link to another resource.
118 | *
119 | * @typedef Link
120 | * @type {object}
121 | * @property {string} href The URL to the resource.
122 | * @property {?string} rel Relation type
123 | * @property {?string} type Media type
124 | * @property {?string} title Human-readable title
125 | * @property {?Array.} roles A list of roles, if link is originating from an asset.
126 | */
127 |
128 | /**
129 | * @typedef LogsAPI
130 | * @type {object}
131 | * @property {Array.} logs
132 | * @property {Array. } links
133 | */
134 |
135 | /**
136 | * A log entry.
137 | *
138 | * @typedef Log
139 | * @type {object}
140 | * @property {string} id
141 | * @property {string} code
142 | * @property {string} level
143 | * @property {string} message
144 | * @property {*} data
145 | * @property {Array.>} path
146 | * @property {Array. } links
147 | */
148 |
149 | /**
150 | * Default OpenID Connect Client as returned by the API.
151 | *
152 | * @typedef OidcClient
153 | * @type {object}
154 | * @property {string} id Client ID
155 | * @property {Array.} grant_types Supported Grant Types
156 | * @property {Array.} redirect_urls Allowed Redirect URLs
157 | */
158 |
159 | /**
160 | * OpenID Connect Provider details as returned by the API.
161 | *
162 | * @augments AuthProviderMeta
163 | * @typedef OidcProviderMeta
164 | * @type {object}
165 | * @property {string} id Provider identifier.
166 | * @property {string} title Title for the authentication method.
167 | * @property {string} description Description for the authentication method.
168 | * @property {string} issuer The OpenID Connect issuer location (authority).
169 | * @property {Array.} scopes OpenID Connect Scopes
170 | * @property {Array.} default_clients Default OpenID Connect Clients
171 | * @property {Array. } links Links
172 | */
173 |
174 | /**
175 | * Connection options.
176 | *
177 | * @typedef Options
178 | * @type {object}
179 | * @property {boolean} addNamespaceToProcess Add a namespace property to processes if set to `true`. Defaults to `false`.
180 | */
181 |
182 | /**
183 | * @typedef Processes
184 | * @type {object}
185 | * @property {Array.} processes
186 | * @property {Array. } links
187 | * @property {?Array.} namespaces EXPERIMENTAL!
188 | * @property {Array.} ["federation:missing"] A list of backends from the federation that are missing in the response data.
189 | */
190 |
191 | /**
192 | * An openEO processing chain.
193 | *
194 | * @typedef Process
195 | * @type {object.}
196 | */
197 |
198 | /**
199 | * A back-end in the federation.
200 | *
201 | * @typedef FederationBackend
202 | * @type {object}
203 | * @property {string} id ID of the back-end within the federation.
204 | * @property {string} url URL to the versioned API endpoint of the back-end.
205 | * @property {string} title Name of the back-end.
206 | * @property {string} description A description of the back-end and its specifics.
207 | * @property {string} status Current status of the back-end (online or offline).
208 | * @property {string} last_status_check The time at which the status of the back-end was checked last, formatted as a RFC 3339 date-time.
209 | * @property {string} last_successful_check If the `status` is `offline`: The time at which the back-end was checked and available the last time. Otherwise, this is equal to the property `last_status_check`. Formatted as a RFC 3339 date-time.
210 | * @property {boolean} experimental Declares the back-end to be experimental.
211 | * @property {boolean} deprecated Declares the back-end to be deprecated.
212 | */
213 |
214 | /**
215 | * An array, but enriched with additional details from an openEO API response.
216 | *
217 | * Adds two properties: `links` and `federation:missing`.
218 | *
219 | * @typedef ResponseArray
220 | * @augments Array
221 | * @type {Array.<*>}
222 | * @property {Array. } links A list of related links.
223 | * @property {Array.} ["federation:missing"] A list of backends from the federation that are missing in the response data.
224 | */
225 |
226 | /**
227 | * @typedef ServiceType
228 | * @type {object.}
229 | */
230 |
231 | /**
232 | * @typedef SyncResult
233 | * @type {object}
234 | * @property {Stream.Readable|Blob} data The data as `Stream` in NodeJS environments or as `Blob` in browsers.
235 | * @property {?number} costs The costs for the request in the currency exposed by the back-end.
236 | * @property {?string} type The content media type returned by the back-end.
237 | * @property {Array.} logs Array of log entries as specified in the API.
238 | */
239 |
240 | /**
241 | * @typedef UdfRuntime
242 | * @type {object.}
243 | */
244 |
245 | /**
246 | * @typedef UserAccountStorage
247 | * @type {object}
248 | * @property {number} free in bytes as integer
249 | * @property {number} quota in bytes as integer
250 | */
251 |
252 | /**
253 | * @typedef UserAccount
254 | * @type {object}
255 | * @property {string} user_id
256 | * @property {?string} name
257 | * @property {?string} default_plan
258 | * @property {?UserAccountStorage} storage
259 | * @property {?number} budget
260 | * @property {?Array. } links
261 | */
262 |
263 | /**
264 | * An array, but enriched with additional details from an openEO API response.
265 | *
266 | * Adds the property `federation:backends`.
267 | *
268 | * @typedef ValidationResult
269 | * @augments Array
270 | * @type {Array.}
271 | * @property {Array.} ["federation:backends"] The back-ends that support / do not support the process.
272 | */
273 |
--------------------------------------------------------------------------------
/src/userfile.js:
--------------------------------------------------------------------------------
1 | const Environment = require('./env');
2 | const BaseEntity = require('./baseentity');
3 |
4 | /**
5 | * A File on the user workspace.
6 | *
7 | * @augments BaseEntity
8 | */
9 | class UserFile extends BaseEntity {
10 |
11 | /**
12 | * Creates an object representing a file on the user workspace.
13 | *
14 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
15 | * @param {string} path - The path to the file, relative to the user workspace and without user ID.
16 | */
17 | constructor(connection, path) {
18 | super(connection, ["path", "size", "modified"]);
19 | /**
20 | * Path to the file, relative to the user's directory.
21 | * @readonly
22 | * @public
23 | * @type {string}
24 | */
25 | this.path = path;
26 | /**
27 | * File size in bytes as integer.
28 | * @readonly
29 | * @public
30 | * @type {number}
31 | */
32 | this.size = undefined;
33 | /**
34 | * Date and time the file has lastly been modified, formatted as a RFC 3339 date-time.
35 | * @readonly
36 | * @public
37 | * @type {string}
38 | */
39 | this.modified = undefined;
40 | }
41 |
42 | /**
43 | * Downloads a file from the user workspace into memory.
44 | *
45 | * This method has different behaviour depending on the environment.
46 | * Returns a stream in a NodeJS environment or a Blob in a browser environment.
47 | *
48 | * @async
49 | * @returns {Promise} - Return value depends on the target and environment, see method description for details.
50 | * @throws {Error}
51 | */
52 | async retrieveFile() {
53 | return await this.connection.download('/files/' + this.path, true);
54 | }
55 |
56 | /**
57 | * Downloads a file from the user workspace and saves it.
58 | *
59 | * This method has different behaviour depending on the environment.
60 | * In a NodeJS environment writes the downloaded file to the target location on the file system.
61 | * In a browser environment offers the file for downloading using the specified name (folders are not supported).
62 | *
63 | * @async
64 | * @param {string} target - The target, see method description for details.
65 | * @returns {Promise|void>} - Return value depends on the target and environment, see method description for details.
66 | * @throws {Error}
67 | */
68 | async downloadFile(target) {
69 | let data = await this.connection.download('/files/' + this.path, true);
70 | // @ts-ignore
71 | return await Environment.saveToFile(data, target);
72 | }
73 |
74 | /**
75 | * A callback that is executed on upload progress updates.
76 | *
77 | * @callback uploadStatusCallback
78 | * @param {number} percentCompleted - The percent (0-100) completed.
79 | * @param {UserFile} file - The file object corresponding to the callback.
80 | */
81 |
82 | /**
83 | * Uploads a file to the user workspace.
84 | * If a file with the name exists, overwrites it.
85 | *
86 | * This method has different behaviour depending on the environment.
87 | * In a nodeJS environment the source must be a path to a file as string.
88 | * In a browser environment the source must be an object from a file upload form.
89 | *
90 | * @async
91 | * @param {*} source - The source, see method description for details.
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.
94 | * @returns {Promise}
95 | * @throws {Error}
96 | */
97 | async uploadFile(source, statusCallback = null, abortController = null) {
98 | let options = {
99 | method: 'put',
100 | url: '/files/' + this.path,
101 | data: Environment.dataForUpload(source),
102 | headers: {
103 | 'Content-Type': 'application/octet-stream'
104 | }
105 | };
106 | if (typeof statusCallback === 'function') {
107 | options.onUploadProgress = (progressEvent) => {
108 | let percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total );
109 | statusCallback(percentCompleted, this);
110 | };
111 | }
112 |
113 | let response = await this.connection._send(options, abortController);
114 | return this.setAll(response.data);
115 | }
116 |
117 | /**
118 | * Deletes the file from the user workspace.
119 | *
120 | * @async
121 | * @throws {Error}
122 | */
123 | async deleteFile() {
124 | await this.connection._delete('/files/' + this.path);
125 | }
126 | }
127 |
128 | module.exports = UserFile;
129 |
--------------------------------------------------------------------------------
/src/userprocess.js:
--------------------------------------------------------------------------------
1 | const BaseEntity = require('./baseentity');
2 | const Utils = require('@openeo/js-commons/src/utils');
3 |
4 | /**
5 | * A Stored Process Graph.
6 | *
7 | * @augments BaseEntity
8 | */
9 | class UserProcess extends BaseEntity {
10 |
11 | /**
12 | * Creates an object representing a process graph stored at the back-end.
13 | *
14 | * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
15 | * @param {string} id - ID of a stored process graph.
16 | */
17 | constructor(connection, id) {
18 | super(connection, [
19 | "id",
20 | "summary",
21 | "description",
22 | "categories",
23 | "parameters",
24 | "returns",
25 | "deprecated",
26 | "experimental",
27 | "exceptions",
28 | "examples",
29 | "links",
30 | ["process_graph", "processGraph"]
31 | ]);
32 | /**
33 | * The identifier of the process.
34 | * @public
35 | * @readonly
36 | * @type {string}
37 | */
38 | this.id = id;
39 | /**
40 | * @public
41 | * @readonly
42 | * @type {?string}
43 | */
44 | this.summary = undefined;
45 | /**
46 | * @public
47 | * @readonly
48 | * @type {?string}
49 | */
50 | this.description = undefined;
51 | /**
52 | * A list of categories.
53 | * @public
54 | * @readonly
55 | * @type {?Array.}
56 | */
57 | this.categories = undefined;
58 | /**
59 | * A list of parameters.
60 | *
61 | * @public
62 | * @readonly
63 | * @type {?Array.>}
64 | */
65 | this.parameters = undefined;
66 | /**
67 | * Description of the data that is returned by this process.
68 | * @public
69 | * @readonly
70 | * @type {?object.}
71 | */
72 | this.returns = undefined;
73 | /**
74 | * Specifies that the process or parameter is deprecated with the potential to be removed in any of the next versions.
75 | * @public
76 | * @readonly
77 | * @type {?boolean}
78 | */
79 | this.deprecated = undefined;
80 | /**
81 | * Declares the process or parameter to be experimental, which means that it is likely to change or may produce unpredictable behaviour.
82 | * @public
83 | * @readonly
84 | * @type {?boolean}
85 | */
86 | this.experimental = undefined;
87 | /**
88 | * Declares any exceptions (errors) that might occur during execution of this process.
89 | * @public
90 | * @readonly
91 | * @type {?object.}
92 | */
93 | this.exceptions = undefined;
94 | /**
95 | * @public
96 | * @readonly
97 | * @type {?Array.>}
98 | */
99 | this.examples = undefined;
100 | /**
101 | * Links related to this process.
102 | * @public
103 | * @readonly
104 | * @type {?Array. }
105 | */
106 | this.links = undefined;
107 | /**
108 | * @public
109 | * @readonly
110 | * @type {?object.}
111 | */
112 | this.processGraph = undefined;
113 | }
114 |
115 | /**
116 | * Updates the data stored in this object by requesting the process graph metadata from the back-end.
117 | *
118 | * @async
119 | * @returns {Promise} The updated process graph object (this).
120 | * @throws {Error}
121 | */
122 | async describeUserProcess() {
123 | let response = await this.connection._get('/process_graphs/' + this.id);
124 | if (!Utils.isObject(response.data) || typeof response.data.id !== 'string') {
125 | throw new Error('Invalid response received for user process');
126 | }
127 | this.connection.processes.add(response.data, 'user');
128 | return this.setAll(response.data);
129 | }
130 |
131 | /**
132 | * Modifies the stored process graph at the back-end and afterwards updates this object, too.
133 | *
134 | * @async
135 | * @param {object} parameters - An object with properties to update, each of them is optional, but at least one of them must be specified. Additional properties can be set if the server supports them.
136 | * @param {Process} parameters.process - A new process.
137 | * @param {string} parameters.title - A new title.
138 | * @param {string} parameters.description - A new description.
139 | * @returns {Promise} The updated process graph object (this).
140 | * @throws {Error}
141 | */
142 | async replaceUserProcess(parameters) {
143 | await this.connection._put('/process_graphs/' + this.id, this._convertToRequest(parameters));
144 | if (this._supports('describeUserProcess')) {
145 | return this.describeUserProcess();
146 | }
147 | else {
148 | let obj = this.setAll(parameters);
149 | this.connection.processes.add(obj.toJSON(), 'user');
150 | return obj;
151 | }
152 | }
153 |
154 | /**
155 | * Deletes the stored process graph from the back-end.
156 | *
157 | * @async
158 | * @throws {Error}
159 | */
160 | async deleteUserProcess() {
161 | await this.connection._delete('/process_graphs/' + this.id);
162 | this.connection.processes.remove(this.id, 'user');
163 | }
164 | }
165 |
166 | module.exports = UserProcess;
167 |
--------------------------------------------------------------------------------
/tests/basic.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | const { OpenEO, OidcProvider } = require('../src/openeo');
3 | const packageInfo = require('../package.json');
4 |
5 | describe('Client Basics', () => {
6 | test('Check version number', () => {
7 | expect(OpenEO.clientVersion()).toBe(packageInfo.version);
8 | });
9 |
10 | describe('OIDC', () => {
11 | test('isSupported', async () => {
12 | expect(OidcProvider.isSupported()).toBeTruthy();
13 | });
14 | });
15 | });
--------------------------------------------------------------------------------
/tests/builder.array_create.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | describe('Process Graph Builder (array_create)', () => {
3 |
4 | const { Builder } = require('../src/openeo');
5 | const expectedProcess = require('./data/builder.array_create.example.json');
6 | const array_create = {
7 | "id": "array_create",
8 | "description": "Creates a new array, which by default is empty.\n\nThe second parameter `repeat` allows to add the given array multiple times to the new array.\n\nIn most cases you can simply pass a (native) array to processes directly, but this process is especially useful to create a new array that is getting returned by a child process, for example in ``apply_dimension()``.",
9 | "parameters": [
10 | {
11 | "name": "data",
12 | "description": "A (native) array to fill the newly created array with. Defaults to an empty array.",
13 | "optional": true,
14 | "default": [],
15 | "schema": {
16 | "description": "Any data type is allowed."
17 | }
18 | },
19 | {
20 | "name": "repeat",
21 | "description": "The number of times the (native) array specified in `data` is repeatedly added after each other to the new array being created. Defaults to `1`.",
22 | "optional": true,
23 | "default": 1,
24 | "schema": {
25 | "type": "integer",
26 | "minimum": 1
27 | }
28 | }
29 | ],
30 | "returns": {
31 | "description": "The newly created array.",
32 | "schema": {
33 | "type": "array",
34 | "items": {
35 | "description": "Any data type is allowed."
36 | }
37 | }
38 | }
39 | };
40 |
41 | var builder;
42 | test('Add array_create to builder', async () => {
43 | // Create builder
44 | builder = await Builder.fromVersion('1.0.0');
45 | expect(builder instanceof Builder).toBeTruthy();
46 |
47 | // Add missing process array_create
48 | builder.addProcessSpec(array_create);
49 | // Check that process is supported now
50 | expect(builder.supports('array_create')).toBeTruthy();
51 | // Check that process has been added correctly
52 | expect(builder.spec('array_create')).toEqual(array_create);
53 |
54 | });
55 |
56 | test('Implicit array_create in callbacks', () => {
57 | // Create process (graph)
58 | var datacube = builder.load_collection("EXAMPLE", null, null);
59 |
60 | var process = function(data) {
61 | return [data[0], 1];
62 | };
63 | var result = builder.apply_dimension(datacube, process, "bands");
64 | result.result = true;
65 |
66 | let pg = builder.toJSON();
67 |
68 | // Check result
69 | expect(pg).toEqual(expectedProcess);
70 |
71 | });
72 |
73 | });
--------------------------------------------------------------------------------
/tests/builder.evi.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | describe('Process Graph Builder (EVI)', () => {
3 |
4 | const { TESTBACKEND } = require('./config.js');
5 | const FROM_URL = TESTBACKEND + '/v1.0/processes';
6 |
7 | const { OpenEO, Connection, Builder, Parameter, Formula } = require('../src/openeo');
8 |
9 | var con;
10 | test('Connect', async () => {
11 | con = await OpenEO.connect(TESTBACKEND);
12 | expect(con instanceof Connection).toBeTruthy();
13 | });
14 |
15 | test('Processes', async () => {
16 | var procs = await con.listProcesses();
17 | expect(Array.isArray(procs.processes)).toBeTruthy();
18 | var ids = procs.processes.map(x => x.id);
19 | expect(ids).toContain('load_collection');
20 | expect(ids).toContain('reduce_dimension');
21 | expect(ids).toContain('save_result');
22 | expect(ids).toContain('min');
23 | expect(ids).toContain('divide');
24 | expect(ids).toContain('multiply');
25 | expect(ids).toContain('sum');
26 | expect(ids).toContain('subtract');
27 | expect(ids).toContain('array_element');
28 | });
29 |
30 | const expectedProcessEvi = require('./data/builder.evi.example.json');
31 | test('Builder', async () => {
32 | var builder = await con.buildProcess("evi");
33 | build(builder);
34 | });
35 |
36 | test('Builder with Math', async () => {
37 | var builder = await con.buildProcess("evi");
38 | build(builder, true);
39 | });
40 |
41 | test('Builder from URL', async () => {
42 | var builder = await Builder.fromURL(FROM_URL);
43 | builder.id = "evi";
44 | build(builder);
45 | });
46 |
47 | test('Builder for an openEO processes version', async () => {
48 | var builder = await Builder.fromVersion('1.0.0');
49 | builder.id = "evi";
50 | build(builder);
51 | });
52 |
53 | function build(builder, math = false) {
54 | expect(builder instanceof Builder).toBeTruthy();
55 |
56 | var datacube = builder.load_collection(
57 | new Parameter("collection-id", "string", "The ID of the collection to load"),
58 | {
59 | "west": 16.1,
60 | "east": 16.6,
61 | "north": 48.6,
62 | "south": 47.2
63 | },
64 | ["2018-01-01", "2018-02-01"],
65 | ["B02", "B04", "B08"]
66 | );
67 | var eviAlgorithm;
68 | var expectedProcess;
69 | if (math) {
70 | expectedProcess = require('./data/builder.math.evi.example.json');
71 | eviAlgorithm = new Formula('2.5 * (($B08 - $B04) / ($$offset + $B08 + 6 * $B04 + -7.5 * $B02))');
72 | }
73 | else {
74 | expectedProcess = expectedProcessEvi;
75 | eviAlgorithm = function(data, context) {
76 | var nir = data["B08"];
77 | var red = data["B04"];
78 | var blue = data["B02"];
79 | var blue2 = data["B02"]; // Not needed for the algorithm, but tests whether multiple calls don't result in multiple array_element nodes.
80 | return this.multiply(
81 | 2.5,
82 | this.divide(
83 | this.subtract(nir, red),
84 | this.sum([
85 | context["offset"],
86 | nir,
87 | this.multiply(6, red),
88 | this.multiply(-7.5, blue)
89 | ])
90 | )
91 | );
92 | };
93 | }
94 | var evi = builder.reduce_dimension(datacube, eviAlgorithm, "bands", {offset: 1}).description("Compute the EVI. Formula: 2.5 * (NIR - RED) / ($offset + NIR + 6*RED + -7.5*BLUE)");
95 |
96 | var minTime = builder.reduce_dimension(
97 | evi,
98 | function(data) {
99 | return this.min(data);
100 | },
101 | "t"
102 | );
103 | var saved = builder.save_result(minTime, "PNG");
104 | saved.result = true;
105 | let pg = builder.toJSON();
106 | expect(Object.keys(pg.process_graph)).toEqual(Object.keys(expectedProcess.process_graph));
107 | expect(pg).toEqual(expectedProcess);
108 | }
109 | });
--------------------------------------------------------------------------------
/tests/builder.misc.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | describe('Process Graph Builder (S1)', () => {
3 |
4 | const { Builder, Parameter } = require('../src/openeo');
5 | test('No write access to array elements', async () => {
6 | var builder = await Builder.fromVersion('1.0.0');
7 | var mean = function(data) {
8 | data['B1'] = 1;
9 | };
10 | dc = builder.reduce_dimension(new Parameter('dc'), mean, "t");
11 | expect(() => builder.toJSON()).toThrow();
12 | });
13 |
14 | });
--------------------------------------------------------------------------------
/tests/builder.namespaces.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | describe('Process Graph Builder (EVI)', () => {
3 |
4 | const { Builder, Formula } = require('../src/openeo');
5 |
6 | test('Namespaces processes in builder', async () => {
7 | const builder = await Builder.fromVersion('1.1.0');
8 |
9 | // Add namespaced processes
10 | builder.addProcessSpec({id: 'msi'}, "vito");
11 | builder.addProcessSpec({id: 'ndvi'}, "@m.mohr");
12 |
13 | // Create process (graph)
14 | builder.process("msi@vito");
15 | builder.process("ndvi@@m.mohr");
16 |
17 | let pg = builder.toJSON();
18 |
19 | // Check result
20 | expect(pg).toEqual({
21 | "process_graph": {
22 | "msi1": {
23 | process_id: 'msi',
24 | namespace: 'vito',
25 | arguments: {}
26 | },
27 | "ndvi1": {
28 | process_id: 'ndvi',
29 | namespace: '@m.mohr',
30 | arguments: {}
31 | }
32 | }
33 | });
34 |
35 | });
36 | test('Namespaces processes in Formula', async () => {
37 | const builder = await Builder.fromVersion('1.1.0');
38 |
39 | builder.addProcessSpec({
40 | "id": "hello_world",
41 | "parameters": [],
42 | "returns": {
43 | "description": "The numerical value of Pi.",
44 | "schema": {
45 | "type": "number"
46 | }
47 | }
48 | }, "a-b");
49 |
50 | builder.math("hello_world@a-b() + 1");
51 |
52 | let pg = builder.toJSON();
53 |
54 | // Check result
55 | expect(pg).toEqual({
56 | process_graph: {
57 | hellow1: {
58 | process_id: 'hello_world',
59 | namespace: 'a-b',
60 | arguments: {}
61 | },
62 | add1: {
63 | process_id: 'add',
64 | arguments: {
65 | x: {
66 | from_node: "hellow1"
67 | },
68 | y: 1
69 | }
70 | }
71 | }
72 | });
73 |
74 | });
75 | });
--------------------------------------------------------------------------------
/tests/builder.s1.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | describe('Process Graph Builder (S1)', () => {
3 |
4 | const { Builder, Formula } = require('../src/openeo');
5 | const expectedProcess = require('./data/builder.s1.example.json');
6 |
7 | test('S1 with callback (function)', async () => build(false));
8 | test('S1 with callback (arrow function)', async () => build(false, true));
9 | test('S1 with Formula', async () => build(true));
10 |
11 | async function build(useFormula, useArrow = false) {
12 | // Create builder
13 | var builder = await Builder.fromVersion('1.0.0');
14 | expect(builder instanceof Builder).toBeTruthy();
15 |
16 | // We are now loading the Sentinel-1 data over the Area of Interest
17 | var datacube = builder.load_collection(
18 | "COPERNICUS/S1_GRD",
19 | {west: 16.06, south: 48.06, east: 16.65, north: 48.35},
20 | [new Date(Date.UTC(2017, 2, 1)), new Date(Date.UTC(2017, 5, 1))], // Check whether date objects are converted
21 | ["VV"]
22 | );
23 |
24 | // Since we are creating a monthly RGB composite, we need three separated time ranges (March aas R, April as G and May as G).
25 | // Therefore, we split the datacube into three datacubes using a temporal filter.
26 | var march = builder.filter_temporal(datacube, ["2017-03-01", "2017-04-01"]);
27 | var april = builder.filter_temporal(datacube, ["2017-04-01", "2017-05-01"]);
28 | var may = builder.filter_temporal(datacube, ["2017-05-01", "2017-06-01"]);
29 |
30 | // We aggregate the timeseries values into a single image by reducing the time dimension using a mean reducer.
31 | var mean = function(data) {
32 | return this.mean(data);
33 | };
34 | march = builder.reduce_dimension(march, mean, "t");
35 | april = builder.reduce_dimension(april, mean, "t");
36 | may = builder.reduce_dimension(may, mean, "t");
37 |
38 | // Now the three images will be combined into the temporal composite.
39 | // We rename the bands to R, G and B as otherwise the bands are overlapping and the merge process would fail.
40 | march = builder.rename_labels(march, "bands", ["R"], ["VV"]);
41 | april = builder.rename_labels(april, "bands", ["G"], ["VV"]);
42 | may = builder.rename_labels(may, "bands", ["B"], ["VV"]);
43 |
44 | datacube = builder.merge_cubes(march, april);
45 | datacube = builder.merge_cubes(datacube, may);
46 |
47 | // To make the values match the RGB values from 0 to 255 in a PNG file, we need to scale them.
48 | // We can simplify expressing math formulas using the openEO Formula parser.
49 | let scale;
50 | if (useFormula) {
51 | scale = new Formula("linear_scale_range(x, -20, -5, 0, 255)");
52 | }
53 | else if (useArrow) {
54 | scale = (x, c, b) => b.linear_scale_range(x, -20, -5, 0, 255);
55 | }
56 | else {
57 | scale = function(x) {
58 | return this.linear_scale_range(x, -20, -5, 0, 255);
59 | };
60 | }
61 | datacube = builder.apply(datacube, scale);
62 |
63 | // Finally, save the result as PNG file.
64 | // In the options we specify which band should be used for "red", "green" and "blue" color.
65 | datacube = builder.save_result(datacube, "PNG", {
66 | red: "R",
67 | green: "G",
68 | blue: "B"
69 | });
70 |
71 | datacube.result = true;
72 |
73 | let pg = builder.toJSON();
74 | expect(pg).toEqual(expectedProcess);
75 | }
76 |
77 | });
--------------------------------------------------------------------------------
/tests/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | TESTBACKEND: 'https://earthengine.openeo.org',
3 | TESTPATH: '/v1.0',
4 | TESTUSERNAME: `js-client`,
5 | TESTPASSWORD: 'js-client',
6 | STAC_MIGRATE_VERSION: '1.0.0'
7 | };
--------------------------------------------------------------------------------
/tests/data/builder.array_create.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "process_graph": {
3 | "loadco1": {
4 | "process_id": "load_collection",
5 | "arguments": {
6 | "id": "EXAMPLE",
7 | "spatial_extent": null,
8 | "temporal_extent": null
9 | }
10 | },
11 | "applyd1": {
12 | "process_id": "apply_dimension",
13 | "arguments": {
14 | "data": {
15 | "from_node": "loadco1"
16 | },
17 | "process": {
18 | "process_graph": {
19 | "arraye1": {
20 | "process_id": "array_element",
21 | "arguments": {
22 | "data": {
23 | "from_parameter": "data"
24 | },
25 | "index": 0
26 | }
27 | },
28 | "arrayc1": {
29 | "process_id": "array_create",
30 | "arguments": {
31 | "data": [
32 | {
33 | "from_node": "arraye1"
34 | },
35 | 1
36 | ]
37 | },
38 | "result": true
39 | }
40 | }
41 | },
42 | "dimension": "bands"
43 | },
44 | "result": true
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/tests/data/builder.evi.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "evi",
3 | "parameters": [
4 | {
5 | "name": "collection-id",
6 | "description": "The ID of the collection to load",
7 | "schema": {
8 | "type": "string"
9 | }
10 | }
11 | ],
12 | "process_graph": {
13 | "loadco1": {
14 | "process_id": "load_collection",
15 | "arguments": {
16 | "id": {"from_parameter": "collection-id"},
17 | "spatial_extent": {
18 | "west": 16.1,
19 | "east": 16.6,
20 | "north": 48.6,
21 | "south": 47.2
22 | },
23 | "temporal_extent": ["2018-01-01", "2018-02-01"],
24 | "bands": ["B02", "B04", "B08"]
25 | }
26 | },
27 | "reduce1": {
28 | "process_id": "reduce_dimension",
29 | "description": "Compute the EVI. Formula: 2.5 * (NIR - RED) / ($offset + NIR + 6*RED + -7.5*BLUE)",
30 | "arguments": {
31 | "data": {"from_node": "loadco1"},
32 | "dimension": "bands",
33 | "reducer": {
34 | "process_graph": {
35 | "arraye1": {
36 | "process_id": "array_element",
37 | "arguments": {
38 | "data": {"from_parameter": "data"},
39 | "label": "B08"
40 | }
41 | },
42 | "arraye2": {
43 | "process_id": "array_element",
44 | "arguments": {
45 | "data": {"from_parameter": "data"},
46 | "label": "B04"
47 | }
48 | },
49 | "arraye3": {
50 | "process_id": "array_element",
51 | "arguments": {
52 | "data": {"from_parameter": "data"},
53 | "label": "B02"
54 | }
55 | },
56 | "arraye4": {
57 | "process_id": "array_element",
58 | "arguments": {
59 | "data": {"from_parameter": "context"},
60 | "label": "offset"
61 | }
62 | },
63 | "subtra1": {
64 | "process_id": "subtract",
65 | "arguments": {
66 | "x": {"from_node": "arraye1"},
67 | "y": {"from_node": "arraye2"}
68 | }
69 | },
70 | "multip1": {
71 | "process_id": "multiply",
72 | "arguments": {
73 | "x": 6,
74 | "y": {"from_node": "arraye2"}
75 | }
76 | },
77 | "multip2": {
78 | "process_id": "multiply",
79 | "arguments": {
80 | "x": -7.5,
81 | "y": {"from_node": "arraye3"}
82 | }
83 | },
84 | "sum1": {
85 | "process_id": "sum",
86 | "arguments": {
87 | "data": [{"from_node": "arraye4"}, {"from_node": "arraye1"}, {"from_node": "multip1"}, {"from_node": "multip2"}]
88 | }
89 | },
90 | "divide1": {
91 | "process_id": "divide",
92 | "arguments": {
93 | "x": {"from_node": "subtra1"},
94 | "y": {"from_node": "sum1"}
95 | }
96 | },
97 | "multip3": {
98 | "process_id": "multiply",
99 | "arguments": {
100 | "x": 2.5,
101 | "y": {"from_node": "divide1"}
102 | },
103 | "result": true
104 | }
105 | }
106 | },
107 | "context": {
108 | "offset": 1
109 | }
110 | }
111 | },
112 | "reduce2": {
113 | "process_id": "reduce_dimension",
114 | "arguments": {
115 | "data": {"from_node": "reduce1"},
116 | "dimension": "t",
117 | "reducer": {
118 | "process_graph": {
119 | "min1": {
120 | "process_id": "min",
121 | "arguments": {
122 | "data": {"from_parameter": "data"}
123 | },
124 | "result": true
125 | }
126 | }
127 | }
128 | }
129 | },
130 | "savere1": {
131 | "process_id": "save_result",
132 | "arguments": {
133 | "data": {"from_node": "reduce2"},
134 | "format": "PNG"
135 | },
136 | "result": true
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/tests/data/builder.math.evi.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "evi",
3 | "parameters": [
4 | {
5 | "name": "collection-id",
6 | "description": "The ID of the collection to load",
7 | "schema": {
8 | "type": "string"
9 | }
10 | }
11 | ],
12 | "process_graph": {
13 | "loadco1": {
14 | "process_id": "load_collection",
15 | "arguments": {
16 | "id": {"from_parameter": "collection-id"},
17 | "spatial_extent": {
18 | "west": 16.1,
19 | "east": 16.6,
20 | "north": 48.6,
21 | "south": 47.2
22 | },
23 | "temporal_extent": ["2018-01-01", "2018-02-01"],
24 | "bands": ["B02", "B04", "B08"]
25 | }
26 | },
27 | "reduce1": {
28 | "process_id": "reduce_dimension",
29 | "description": "Compute the EVI. Formula: 2.5 * (NIR - RED) / ($offset + NIR + 6*RED + -7.5*BLUE)",
30 | "arguments": {
31 | "data": {"from_node": "loadco1"},
32 | "dimension": "bands",
33 | "reducer": {
34 | "process_graph": {
35 | "arraye1": {
36 | "process_id": "array_element",
37 | "arguments": {
38 | "data": {"from_parameter": "data"},
39 | "label": "B08"
40 | }
41 | },
42 | "arraye2": {
43 | "process_id": "array_element",
44 | "arguments": {
45 | "data": {"from_parameter": "data"},
46 | "label": "B04"
47 | }
48 | },
49 | "arraye3": {
50 | "process_id": "array_element",
51 | "arguments": {
52 | "data": {"from_parameter": "context"},
53 | "label": "offset"
54 | }
55 | },
56 | "arraye4": {
57 | "process_id": "array_element",
58 | "arguments": {
59 | "data": {"from_parameter": "data"},
60 | "label": "B02"
61 | }
62 | },
63 | "subtra1": {
64 | "process_id": "subtract",
65 | "arguments": {
66 | "x": {"from_node": "arraye1"},
67 | "y": {"from_node": "arraye2"}
68 | }
69 | },
70 | "multip1": {
71 | "process_id": "multiply",
72 | "arguments": {
73 | "x": 6,
74 | "y": {"from_node": "arraye2"}
75 | }
76 | },
77 | "multip2": {
78 | "process_id": "multiply",
79 | "arguments": {
80 | "x": -7.5,
81 | "y": {"from_node": "arraye4"}
82 | }
83 | },
84 | "add1": {
85 | "process_id": "add",
86 | "arguments": {
87 | "x": {"from_node": "arraye3"},
88 | "y": {"from_node": "arraye1"}
89 | }
90 | },
91 | "add2": {
92 | "process_id": "add",
93 | "arguments": {
94 | "x": {"from_node": "add1"},
95 | "y": {"from_node": "multip1"}
96 | }
97 | },
98 | "add3": {
99 | "process_id": "add",
100 | "arguments": {
101 | "x": {"from_node": "add2"},
102 | "y": {"from_node": "multip2"}
103 | }
104 | },
105 | "divide1": {
106 | "process_id": "divide",
107 | "arguments": {
108 | "x": {"from_node": "subtra1"},
109 | "y": {"from_node": "add3"}
110 | }
111 | },
112 | "multip3": {
113 | "process_id": "multiply",
114 | "arguments": {
115 | "x": 2.5,
116 | "y": {"from_node": "divide1"}
117 | },
118 | "result": true
119 | }
120 | }
121 | },
122 | "context": {
123 | "offset": 1
124 | }
125 | }
126 | },
127 | "reduce2": {
128 | "process_id": "reduce_dimension",
129 | "arguments": {
130 | "data": {"from_node": "reduce1"},
131 | "dimension": "t",
132 | "reducer": {
133 | "process_graph": {
134 | "min1": {
135 | "process_id": "min",
136 | "arguments": {
137 | "data": {"from_parameter": "data"}
138 | },
139 | "result": true
140 | }
141 | }
142 | }
143 | }
144 | },
145 | "savere1": {
146 | "process_id": "save_result",
147 | "arguments": {
148 | "data": {"from_node": "reduce2"},
149 | "format": "PNG"
150 | },
151 | "result": true
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/tests/data/builder.s1.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "process_graph": {
3 | "loadco1": {
4 | "process_id": "load_collection",
5 | "arguments": {
6 | "id": "COPERNICUS/S1_GRD",
7 | "spatial_extent": {
8 | "west": 16.06,
9 | "south": 48.06,
10 | "east": 16.65,
11 | "north": 48.35
12 | },
13 | "temporal_extent": [
14 | "2017-03-01T00:00:00.000Z",
15 | "2017-06-01T00:00:00.000Z"
16 | ],
17 | "bands": [
18 | "VV"
19 | ]
20 | }
21 | },
22 | "filter1": {
23 | "process_id": "filter_temporal",
24 | "arguments": {
25 | "data": {
26 | "from_node": "loadco1"
27 | },
28 | "extent": [
29 | "2017-03-01",
30 | "2017-04-01"
31 | ]
32 | }
33 | },
34 | "filter2": {
35 | "process_id": "filter_temporal",
36 | "arguments": {
37 | "data": {
38 | "from_node": "loadco1"
39 | },
40 | "extent": [
41 | "2017-04-01",
42 | "2017-05-01"
43 | ]
44 | }
45 | },
46 | "filter3": {
47 | "process_id": "filter_temporal",
48 | "arguments": {
49 | "data": {
50 | "from_node": "loadco1"
51 | },
52 | "extent": [
53 | "2017-05-01",
54 | "2017-06-01"
55 | ]
56 | }
57 | },
58 | "reduce1": {
59 | "process_id": "reduce_dimension",
60 | "arguments": {
61 | "data": {
62 | "from_node": "filter1"
63 | },
64 | "reducer": {
65 | "process_graph": {
66 | "mean1": {
67 | "process_id": "mean",
68 | "arguments": {
69 | "data": {
70 | "from_parameter": "data"
71 | }
72 | },
73 | "result": true
74 | }
75 | }
76 | },
77 | "dimension": "t"
78 | }
79 | },
80 | "reduce2": {
81 | "process_id": "reduce_dimension",
82 | "arguments": {
83 | "data": {
84 | "from_node": "filter2"
85 | },
86 | "reducer": {
87 | "process_graph": {
88 | "mean1": {
89 | "process_id": "mean",
90 | "arguments": {
91 | "data": {
92 | "from_parameter": "data"
93 | }
94 | },
95 | "result": true
96 | }
97 | }
98 | },
99 | "dimension": "t"
100 | }
101 | },
102 | "reduce3": {
103 | "process_id": "reduce_dimension",
104 | "arguments": {
105 | "data": {
106 | "from_node": "filter3"
107 | },
108 | "reducer": {
109 | "process_graph": {
110 | "mean1": {
111 | "process_id": "mean",
112 | "arguments": {
113 | "data": {
114 | "from_parameter": "data"
115 | }
116 | },
117 | "result": true
118 | }
119 | }
120 | },
121 | "dimension": "t"
122 | }
123 | },
124 | "rename1": {
125 | "process_id": "rename_labels",
126 | "arguments": {
127 | "data": {
128 | "from_node": "reduce1"
129 | },
130 | "dimension": "bands",
131 | "target": [
132 | "R"
133 | ],
134 | "source": [
135 | "VV"
136 | ]
137 | }
138 | },
139 | "rename2": {
140 | "process_id": "rename_labels",
141 | "arguments": {
142 | "data": {
143 | "from_node": "reduce2"
144 | },
145 | "dimension": "bands",
146 | "target": [
147 | "G"
148 | ],
149 | "source": [
150 | "VV"
151 | ]
152 | }
153 | },
154 | "rename3": {
155 | "process_id": "rename_labels",
156 | "arguments": {
157 | "data": {
158 | "from_node": "reduce3"
159 | },
160 | "dimension": "bands",
161 | "target": [
162 | "B"
163 | ],
164 | "source": [
165 | "VV"
166 | ]
167 | }
168 | },
169 | "mergec1": {
170 | "process_id": "merge_cubes",
171 | "arguments": {
172 | "cube1": {
173 | "from_node": "rename1"
174 | },
175 | "cube2": {
176 | "from_node": "rename2"
177 | }
178 | }
179 | },
180 | "mergec2": {
181 | "process_id": "merge_cubes",
182 | "arguments": {
183 | "cube1": {
184 | "from_node": "mergec1"
185 | },
186 | "cube2": {
187 | "from_node": "rename3"
188 | }
189 | }
190 | },
191 | "apply1": {
192 | "process_id": "apply",
193 | "arguments": {
194 | "data": {
195 | "from_node": "mergec2"
196 | },
197 | "process": {
198 | "process_graph": {
199 | "linear1": {
200 | "process_id": "linear_scale_range",
201 | "arguments": {
202 | "x": {
203 | "from_parameter": "x"
204 | },
205 | "inputMin": -20,
206 | "inputMax": -5,
207 | "outputMin": 0,
208 | "outputMax": 255
209 | },
210 | "result": true
211 | }
212 | }
213 | }
214 | }
215 | },
216 | "savere1": {
217 | "process_id": "save_result",
218 | "arguments": {
219 | "data": {
220 | "from_node": "apply1"
221 | },
222 | "format": "PNG",
223 | "options": {
224 | "red": "R",
225 | "green": "G",
226 | "blue": "B"
227 | }
228 | },
229 | "result": true
230 | }
231 | }
232 | }
--------------------------------------------------------------------------------
/tests/data/gee/collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "stac_version": "1.0.0",
3 | "id": "AAFC/ACI",
4 | "title": "Canada AAFC Annual Crop Inventory",
5 | "gee:type": "image_collection",
6 | "description": "Starting in 2009, the Earth Observation Team of the Science and Technology\nBranch (STB) at Agriculture and Agri-Food Canada (AAFC) began the process\nof generating annual crop type digital maps. Focusing on the Prairie\nProvinces in 2009 and 2010, a Decision Tree (DT) based methodology was\napplied using optical (Landsat-5, AWiFS, DMC) and radar (Radarsat-2) based\nsatellite images. Beginning with the 2011 growing season, this activity has\nbeen extended to other provinces in support of a national crop inventory.\nTo date this approach can consistently deliver a crop inventory that meets\nthe overall target accuracy of at least 85% at a final spatial resolution of\n30m (56m in 2009 and 2010).\n",
7 | "license": "OGL-Canada-2.0",
8 | "links": [],
9 | "keywords": ["aafc", "canada", "crop", "landcover"],
10 | "providers": [
11 | {
12 | "name": "Agriculture and Agri-Food Canada",
13 | "roles": ["producer", "licensor"],
14 | "url": "https://open.canada.ca/data/en/dataset/ba2645d5-4458-414d-b196-6303ac06c1c9"
15 | },
16 | {
17 | "name": "Google Earth Engine",
18 | "roles": ["host"],
19 | "url": "https://developers.google.com/earth-engine/datasets/catalog/AAFC_ACI"
20 | }
21 | ],
22 | "extent": {
23 | "spatial": { "bbox": [[-135.17, 36.83, -51.24, 62.25]] },
24 | "temporal": {
25 | "interval": [["2009-01-01T00:00:00Z", "2023-01-01T00:00:00Z"]]
26 | }
27 | },
28 | "summaries": {},
29 | "sci:citation": "Agriculture and Agri-Food Canada Annual Crop Inventory. {YEAR}",
30 | "stac_extensions": [
31 | "https://stac-extensions.github.io/datacube/v2.2.0/schema.json"
32 | ],
33 | "cube:dimensions": {
34 | "x": { "type": "spatial", "axis": "x", "extent": [-135.17, -51.24] },
35 | "y": { "type": "spatial", "axis": "y", "extent": [36.83, 62.25] },
36 | "t": {
37 | "type": "temporal",
38 | "extent": ["2009-01-01T00:00:00Z", "2023-01-01T00:00:00Z"]
39 | },
40 | "bands": { "type": "bands", "values": ["landcover"] }
41 | },
42 | "assets": {}
43 | }
44 |
--------------------------------------------------------------------------------
/tests/data/gee/process.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "min",
3 | "summary": "Minimum value",
4 | "description": "Computes the smallest value of an array of numbers, which is equal to the last element of a sorted (i.e., ordered) version of the array.\n\nAn array with solely no-data values returns the no-data value (or `null`).",
5 | "categories": ["math", "math > statistics", "reducer"],
6 | "parameters": [
7 | {
8 | "name": "data",
9 | "description": "An array of numbers.",
10 | "schema": { "type": "array", "items": { "type": ["number", "null"] } }
11 | }
12 | ],
13 | "returns": {
14 | "description": "The minimum value.",
15 | "schema": { "type": ["number", "null"] }
16 | },
17 | "examples": [
18 | { "arguments": { "data": [1, 0, 3, 2] }, "returns": 0 },
19 | { "arguments": { "data": [5, 2.5, null, -0.7] }, "returns": -0.7 },
20 | { "arguments": { "data": [] }, "returns": null }
21 | ],
22 | "links": [
23 | {
24 | "rel": "about",
25 | "href": "http://mathworld.wolfram.com/Minimum.html",
26 | "title": "Minimum explained by Wolfram MathWorld"
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/tests/data/gee/processgraph.json:
--------------------------------------------------------------------------------
1 | {
2 | "1": {
3 | "process_id": "load_collection",
4 | "arguments": {
5 | "id": "IDAHO_EPSCOR/TERRACLIMATE",
6 | "spatial_extent": null,
7 | "temporal_extent": ["2017-07-01T00:00:00Z", "2017-07-31T23:59:59Z"],
8 | "bands": ["tmmx"]
9 | }
10 | },
11 | "2": {
12 | "process_id": "save_result",
13 | "arguments": {
14 | "data": { "from_node": "3" },
15 | "format": "PNG",
16 | "options": { "epsgCode": 4326 }
17 | },
18 | "result": true
19 | },
20 | "3": {
21 | "process_id": "apply",
22 | "arguments": {
23 | "data": { "from_node": "1" },
24 | "process": {
25 | "process_graph": {
26 | "2": {
27 | "process_id": "linear_scale_range",
28 | "arguments": {
29 | "x": { "from_parameter": "x" },
30 | "inputMin": -150,
31 | "inputMax": 450,
32 | "outputMax": 255
33 | },
34 | "result": true
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/eodc.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | const { OpenEO, Connection, Capabilities } = require('../src/openeo');
3 | const { Utils } = require('@openeo/js-commons');
4 |
5 | jest.setTimeout(30*1000);
6 |
7 | describe('EODC back-end', () => {
8 | const TESTBACKEND = 'https://openeo.eodc.eu';
9 |
10 | const TESTCOLLECTION = 'SENTINEL2_L2A';
11 |
12 | describe('Request Collection Items', () => {
13 |
14 | let con;
15 | // Skip this test for now, EODC back-end has no CORS headers
16 | test.skip('Connect', async () => {
17 | con = await OpenEO.connect(TESTBACKEND);
18 | expect(con instanceof Connection).toBeTruthy();
19 | let cap = con.capabilities();
20 | expect(cap instanceof Capabilities).toBeTruthy();
21 | });
22 |
23 | // Skip this test for now, EODC back-end has no CORS headers
24 | test.skip('Check collection', async () => {
25 | let col = await con.describeCollection(TESTCOLLECTION);
26 | console.log(col.id);
27 | expect(col.id).toBe(TESTCOLLECTION);
28 | expect(col).toHaveProperty("links");
29 | expect(typeof con._getLinkHref(col.links, 'items')).toBe("string");
30 | });
31 |
32 | // Skip this test for now, EODC back-end requires Auth
33 | test.skip('Request three pages of items', async () => {
34 | let page = 1;
35 | let spatialExtent = [5.0,45.0,20.0,50.0];
36 | let temporalExtent = [Date.UTC(2015, 0, 1), Date.UTC(2017, 0, 1)];
37 | let limit = 5;
38 | for await(let response of con.listCollectionItems(TESTCOLLECTION, spatialExtent, temporalExtent, limit)) {
39 | expect(Utils.isObject(response)).toBeTruthy();
40 | expect(response).toHaveProperty('features');
41 | expect(Array.isArray(response.features)).toBeTruthy();
42 | expect(response.features.length).toBe(limit);
43 | page++;
44 | if (page > 3) {
45 | break;
46 | }
47 | }
48 | });
49 |
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/tests/vito.test.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | const { OpenEO, Connection, Capabilities, BasicProvider, OidcProvider } = require('../src/openeo');
3 | const { Utils } = require('@openeo/js-commons');
4 |
5 | jest.setTimeout(30*1000);
6 |
7 | describe('VITO back-end', () => {
8 | const TESTBACKEND = 'https://openeo.vito.be';
9 |
10 | let con;
11 | test('Connect', async () => {
12 | con = await OpenEO.connect(TESTBACKEND, {addNamespaceToProcess: true});
13 | expect(con instanceof Connection).toBeTruthy();
14 | expect(con.options.addNamespaceToProcess).toBeTruthy();
15 | let cap = con.capabilities();
16 | expect(cap instanceof Capabilities).toBeTruthy();
17 | });
18 |
19 | describe('OIDC', () => {
20 | test('listAuthProviders', async () => {
21 | let providers = await con.listAuthProviders();
22 | expect(Array.isArray(providers)).toBeTruthy();
23 | expect(providers.find(p => p instanceof BasicProvider)).toBeDefined();
24 | expect(providers.find(p => p instanceof OidcProvider)).toBeDefined();
25 | });
26 | });
27 |
28 | describe('UDFs', () => {
29 | test('listUdfRuntimes', async () => {
30 | let runtime = "Python";
31 | let udfs = await con.listUdfRuntimes();
32 | expect(Utils.isObject(udfs)).toBeTruthy();
33 | expect(udfs).toHaveProperty(runtime);
34 | expect(Utils.isObject(udfs[runtime])).toBeTruthy();
35 | expect(Utils.isObject(udfs[runtime].versions)).toBeTruthy();
36 | expect(udfs[runtime].type).toBe("language");
37 | });
38 | });
39 |
40 | describe('Request processes from namespace', () => {
41 |
42 | // Not implemented yet by VITO
43 | /* test('Check process namespace list', async () => {
44 | let p = await con.listProcesses();
45 | expect(Array.isArray(p.namespaces)).toBeTruthy();
46 | expect(p.namespaces).toContain("vito");
47 | }); */
48 |
49 | test('Check pre-defined process list does not contain "MSI"', async () => {
50 | let p = await con.listProcesses();
51 | expect(Array.isArray(p.processes)).toBeTruthy();
52 | let msi = p.processes.find(process => process.id === 'MSI');
53 | expect(msi).toBeUndefined();
54 | });
55 |
56 | test('Request processes from namespace "vito"', async () => {
57 | let p = await con.listProcesses('vito');
58 | expect(Array.isArray(p.processes)).toBeTruthy();
59 | let msi = p.processes.find(process => process.id === 'MSI');
60 | expect(Utils.isObject(msi)).toBeTruthy();
61 | expect(msi.namespace).toBe('vito');
62 | expect(msi.description).toBeDefined();
63 | });
64 |
65 | test('Request process "MSI" from namespace "vito"', async () => {
66 | let msi = await con.describeProcess('MSI', 'vito');
67 | expect(Utils.isObject(msi)).toBeTruthy();
68 | expect(msi.id).toBe('MSI');
69 | expect(msi.namespace).toBe('vito');
70 | expect(msi.description).toBeDefined();
71 | expect(msi.process_graph).toBeDefined();
72 | });
73 |
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src"
4 | ],
5 | "compilerOptions": {
6 | // Tells TypeScript to read JS files
7 | "allowJs": true,
8 | // Generate d.ts files
9 | "declaration": true,
10 | // This compiler run should only output d.ts files
11 | "emitDeclarationOnly": true,
12 | // see https://github.com/babel/babel/issues/10237#issuecomment-513028440
13 | "moduleResolution": "node",
14 | // Emit the declaration into a single file. The generated file is invalid and needs some manual adoptions.
15 | "outFile": "openeo.d.ts",
16 | // See https://stackoverflow.com/questions/38209699/error-ts2403-subsequent-variable-declarations-must-have-the-same-type
17 | "skipLibCheck": true
18 | }
19 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
3 | const UnminifiedWebpackPlugin = require('unminified-webpack-plugin');
4 |
5 | module.exports = [
6 | // Web
7 | {
8 | target: "web",
9 | mode: 'production',
10 | entry: './src/openeo.js',
11 | output: {
12 | filename: 'openeo.min.js',
13 | path: path.resolve(__dirname),
14 | libraryTarget: 'umd'
15 | },
16 | externals: {
17 | 'axios': 'axios',
18 | 'buffer': 'Buffer',
19 | 'fs': 'fs',
20 | 'oidc-client': 'Oidc',
21 | 'multihashes': 'multihashes',
22 | 'path': 'path',
23 | 'stream': 'Stream',
24 | 'url': 'url'
25 | },
26 | resolve: {
27 | alias: {
28 | './env$': path.resolve(__dirname, 'src/browser.js')
29 | }
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /\.js$/,
35 | exclude: /node_modules/,
36 | use: {
37 | loader: 'babel-loader',
38 | options: {
39 | presets: ['@babel/preset-env']
40 | }
41 | }
42 | }
43 | ]
44 | },
45 | plugins: [
46 | new UnminifiedWebpackPlugin(),
47 | new BundleAnalyzerPlugin({
48 | analyzerMode: 'static',
49 | openAnalyzer: false
50 | })
51 | ],
52 | }
53 | ];
--------------------------------------------------------------------------------