├── .drone.yml
├── .eslintrc
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── src
├── iexec-server-js-client.js
└── utils.js
└── test
├── .eslintrc
├── Echo
└── iexec-server-js-client.test.js
/.drone.yml:
--------------------------------------------------------------------------------
1 | pipeline:
2 | build:
3 | image: node:6.4.0
4 | commands:
5 | - npm install
6 | - npm run build
7 |
8 | npm:
9 | image: plugins/npm
10 | secrets: [ npm_password ]
11 | username: sulliwane
12 | email: sulliwane@gmail.com
13 | when:
14 | event: tag
15 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": "airbnb-base",
4 | "rules": {
5 | "no-console": 0,
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Coverage directory used by tools like istanbul
12 | coverage
13 |
14 | # node-waf configuration
15 | .lock-wscript
16 |
17 | # Dependency directory
18 | node_modules
19 |
20 | # Optional npm cache directory
21 | .npm
22 |
23 | # Optional REPL history
24 | .node_repl_history
25 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | License: Apache2.0
2 |
3 | Apache License
4 | Version 2.0, January 2004
5 | http://www.apache.org/licenses/
6 |
7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8 |
9 | 1. Definitions.
10 |
11 | "License" shall mean the terms and conditions for use, reproduction,
12 | and distribution as defined by Sections 1 through 9 of this document.
13 |
14 | "Licensor" shall mean the copyright owner or entity authorized by
15 | the copyright owner that is granting the License.
16 |
17 | "Legal Entity" shall mean the union of the acting entity and all
18 | other entities that control, are controlled by, or are under common
19 | control with that entity. For the purposes of this definition,
20 | "control" means (i) the power, direct or indirect, to cause the
21 | direction or management of such entity, whether by contract or
22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
23 | outstanding shares, or (iii) beneficial ownership of such entity.
24 |
25 | "You" (or "Your") shall mean an individual or Legal Entity
26 | exercising permissions granted by this License.
27 |
28 | "Source" form shall mean the preferred form for making modifications,
29 | including but not limited to software source code, documentation
30 | source, and configuration files.
31 |
32 | "Object" form shall mean any form resulting from mechanical
33 | transformation or translation of a Source form, including but
34 | not limited to compiled object code, generated documentation,
35 | and conversions to other media types.
36 |
37 | "Work" shall mean the work of authorship, whether in Source or
38 | Object form, made available under the License, as indicated by a
39 | copyright notice that is included in or attached to the work
40 | (an example is provided in the Appendix below).
41 |
42 | "Derivative Works" shall mean any work, whether in Source or Object
43 | form, that is based on (or derived from) the Work and for which the
44 | editorial revisions, annotations, elaborations, or other modifications
45 | represent, as a whole, an original work of authorship. For the purposes
46 | of this License, Derivative Works shall not include works that remain
47 | separable from, or merely link (or bind by name) to the interfaces of,
48 | the Work and Derivative Works thereof.
49 |
50 | "Contribution" shall mean any work of authorship, including
51 | the original version of the Work and any modifications or additions
52 | to that Work or Derivative Works thereof, that is intentionally
53 | submitted to Licensor for inclusion in the Work by the copyright owner
54 | or by an individual or Legal Entity authorized to submit on behalf of
55 | the copyright owner. For the purposes of this definition, "submitted"
56 | means any form of electronic, verbal, or written communication sent
57 | to the Licensor or its representatives, including but not limited to
58 | communication on electronic mailing lists, source code control systems,
59 | and issue tracking systems that are managed by, or on behalf of, the
60 | Licensor for the purpose of discussing and improving the Work, but
61 | excluding communication that is conspicuously marked or otherwise
62 | designated in writing by the copyright owner as "Not a Contribution."
63 |
64 | "Contributor" shall mean Licensor and any individual or Legal Entity
65 | on behalf of whom a Contribution has been received by Licensor and
66 | subsequently incorporated within the Work.
67 |
68 | 2. Grant of Copyright License. Subject to the terms and conditions of
69 | this License, each Contributor hereby grants to You a perpetual,
70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71 | copyright license to reproduce, prepare Derivative Works of,
72 | publicly display, publicly perform, sublicense, and distribute the
73 | Work and such Derivative Works in Source or Object form.
74 |
75 | 3. Grant of Patent License. Subject to the terms and conditions of
76 | this License, each Contributor hereby grants to You a perpetual,
77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78 | (except as stated in this section) patent license to make, have made,
79 | use, offer to sell, sell, import, and otherwise transfer the Work,
80 | where such license applies only to those patent claims licensable
81 | by such Contributor that are necessarily infringed by their
82 | Contribution(s) alone or by combination of their Contribution(s)
83 | with the Work to which such Contribution(s) was submitted. If You
84 | institute patent litigation against any entity (including a
85 | cross-claim or counterclaim in a lawsuit) alleging that the Work
86 | or a Contribution incorporated within the Work constitutes direct
87 | or contributory patent infringement, then any patent licenses
88 | granted to You under this License for that Work shall terminate
89 | as of the date such litigation is filed.
90 |
91 | 4. Redistribution. You may reproduce and distribute copies of the
92 | Work or Derivative Works thereof in any medium, with or without
93 | modifications, and in Source or Object form, provided that You
94 | meet the following conditions:
95 |
96 | (a) You must give any other recipients of the Work or
97 | Derivative Works a copy of this License; and
98 |
99 | (b) You must cause any modified files to carry prominent notices
100 | stating that You changed the files; and
101 |
102 | (c) You must retain, in the Source form of any Derivative Works
103 | that You distribute, all copyright, patent, trademark, and
104 | attribution notices from the Source form of the Work,
105 | excluding those notices that do not pertain to any part of
106 | the Derivative Works; and
107 |
108 | (d) If the Work includes a "NOTICE" text file as part of its
109 | distribution, then any Derivative Works that You distribute must
110 | include a readable copy of the attribution notices contained
111 | within such NOTICE file, excluding those notices that do not
112 | pertain to any part of the Derivative Works, in at least one
113 | of the following places: within a NOTICE text file distributed
114 | as part of the Derivative Works; within the Source form or
115 | documentation, if provided along with the Derivative Works; or,
116 | within a display generated by the Derivative Works, if and
117 | wherever such third-party notices normally appear. The contents
118 | of the NOTICE file are for informational purposes only and
119 | do not modify the License. You may add Your own attribution
120 | notices within Derivative Works that You distribute, alongside
121 | or as an addendum to the NOTICE text from the Work, provided
122 | that such additional attribution notices cannot be construed
123 | as modifying the License.
124 |
125 | You may add Your own copyright statement to Your modifications and
126 | may provide additional or different license terms and conditions
127 | for use, reproduction, or distribution of Your modifications, or
128 | for any such Derivative Works as a whole, provided Your use,
129 | reproduction, and distribution of the Work otherwise complies with
130 | the conditions stated in this License.
131 |
132 | 5. Submission of Contributions. Unless You explicitly state otherwise,
133 | any Contribution intentionally submitted for inclusion in the Work
134 | by You to the Licensor shall be under the terms and conditions of
135 | this License, without any additional terms or conditions.
136 | Notwithstanding the above, nothing herein shall supersede or modify
137 | the terms of any separate license agreement you may have executed
138 | with Licensor regarding such Contributions.
139 |
140 | 6. Trademarks. This License does not grant permission to use the trade
141 | names, trademarks, service marks, or product names of the Licensor,
142 | except as required for reasonable and customary use in describing the
143 | origin of the Work and reproducing the content of the NOTICE file.
144 |
145 | 7. Disclaimer of Warranty. Unless required by applicable law or
146 | agreed to in writing, Licensor provides the Work (and each
147 | Contributor provides its Contributions) on an "AS IS" BASIS,
148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149 | implied, including, without limitation, any warranties or conditions
150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151 | PARTICULAR PURPOSE. You are solely responsible for determining the
152 | appropriateness of using or redistributing the Work and assume any
153 | risks associated with Your exercise of permissions under this License.
154 |
155 | 8. Limitation of Liability. In no event and under no legal theory,
156 | whether in tort (including negligence), contract, or otherwise,
157 | unless required by applicable law (such as deliberate and grossly
158 | negligent acts) or agreed to in writing, shall any Contributor be
159 | liable to You for damages, including any direct, indirect, special,
160 | incidental, or consequential damages of any character arising as a
161 | result of this License or out of the use or inability to use the
162 | Work (including but not limited to damages for loss of goodwill,
163 | work stoppage, computer failure or malfunction, or any and all
164 | other commercial damages or losses), even if such Contributor
165 | has been advised of the possibility of such damages.
166 |
167 | 9. Accepting Warranty or Additional Liability. While redistributing
168 | the Work or Derivative Works thereof, You may choose to offer,
169 | and charge a fee for, acceptance of support, warranty, indemnity,
170 | or other liability obligations and/or rights consistent with this
171 | License. However, in accepting such obligations, You may act only
172 | on Your own behalf and on Your sole responsibility, not on behalf
173 | of any other Contributor, and only if You agree to indemnify,
174 | defend, and hold each Contributor harmless for any liability
175 | incurred by, or claims asserted against, such Contributor by reason
176 | of your accepting any such warranty or additional liability.
177 |
178 | END OF TERMS AND CONDITIONS
179 |
180 | APPENDIX: How to apply the Apache License to your work.
181 |
182 | To apply the Apache License to your work, attach the following
183 | boilerplate notice, with the fields enclosed by brackets "{}"
184 | replaced with your own identifying information. (Don't include
185 | the brackets!) The text should be enclosed in the appropriate
186 | comment syntax for the file format. We also recommend that a
187 | file or class name and description of purpose be included on the
188 | same "printed page" as the copyright notice for easier
189 | identification within third-party archives.
190 |
191 | Copyright 2016 All in Bits, Inc
192 |
193 | Licensed under the Apache License, Version 2.0 (the "License");
194 | you may not use this file except in compliance with the License.
195 | You may obtain a copy of the License at
196 |
197 | http://www.apache.org/licenses/LICENSE-2.0
198 |
199 | Unless required by applicable law or agreed to in writing, software
200 | distributed under the License is distributed on an "AS IS" BASIS,
201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202 | See the License for the specific language governing permissions and
203 | limitations under the License.
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iexec-server-js-client
2 |
3 | [](https://drone.iex.ec/iExecBlockchainComputing/iexec-server-js-client) [](https://www.npmjs.com/package/iexec-server-js-client) [](LICENSE)
4 |
5 | JS client lib to interact with iExec server REST API
6 |
7 | ## Ressources
8 |
9 | * The iExec server API doc: https://serverapi.iex.ec
10 | * [The iExec SDK](https://github.com/iExecBlockchainComputing/iexec-sdk)
11 | * The iExec main documentation: https://docs.iex.ec
12 |
13 | ## Examples
14 |
15 | Below are examples showcasing the use of the library in the most common worklow:
16 |
17 | ### 1. Create iExec client
18 |
19 | iExec server URL:
20 |
21 | * tesnet (ropsten / rinkeby / kovan): `https://testxw.iex.ec:443`
22 | * mainnet: `https://mainxw.iex.ec:443`
23 |
24 | ```js
25 | const createIEXECClient = require('iexec-server-js-client');
26 | const iexec = createIEXECClient({ server: 'https://testxw.iex.ec:443' });
27 | ```
28 |
29 | ### 2. Auth
30 |
31 | Authenticate before hitting iExec API:
32 |
33 | ```js
34 | iexec.auth(web3.currentProvider, accountAddress).then(({ jwtoken, cookie }) => {
35 | console.log(jwtoken); // this is given by auth.iex.ec server
36 | console.log(cookie); // this is given by iExec server
37 | // hit iExec server API
38 | iexec.getAppByName(deployTxHash).then(console.log); // print app description from deploy txHash
39 | iexec.getWorkByExternalID(submitTxHash).then(console.log); // print work description from submit txHash
40 | });
41 | ```
42 |
43 | If you already have your JWT token, no need to do full auth (avoid wallet signing):
44 |
45 | ```js
46 | iexec.getCookieByJWT('my_jwt_token').then(cookie => {
47 | // hit iExec server API
48 | iexec.getByUID(workUID).then(console.log); // print work description
49 | });
50 | ```
51 |
52 | ### 3. Submit a work
53 |
54 | Call the dapp smart contract "iexecSubmit" method to submit a work (for reference only, not part of this repo library):
55 |
56 | ```js
57 | const oracleJSON = require('iexec-oracle-contract/build/contracts/IexecOracle.json');
58 | const work = '{"cmdline":"10"}'
59 |
60 | const oracleContract = web3.eth
61 | .contract(oracleJSON.abi)
62 | .at(oracleJSON.networks[chainID].address);
63 | const callbackPrice = await oracleContract.callbackPrice();
64 |
65 | const dappContract = web3.eth
66 | .contract(dappSubmitABI)
67 | .at(dappAddress);
68 |
69 | // this is the work submit
70 | const txHash = await dappContract.iexecSubmit(work, {
71 | value: callbackPrice[0].toNumber(),
72 | });
73 | ```
74 |
75 | ### 4. Wait for work result
76 |
77 | After submitting a work through Ethereum, use the transaction hash (txHash) to wait for the work result:
78 |
79 | ```js
80 | iexec
81 | .waitForWorkResult(oracleContract.getWork, txHash)
82 | .then(workResultURI => iexec.createDownloadURI(workResultURI))
83 | .then(console.log); // let user open this URL in the browser to download the work result
84 | ```
85 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iexec-server-js-client",
3 | "version": "2.0.3",
4 | "description": "JS client to interact with iExec REST API",
5 | "main": "dist/iexec-server-js-client.js",
6 | "scripts": {
7 | "test": "jest",
8 | "build": "./node_modules/.bin/babel src --out-dir dist --copy-files"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/iExecBlockchainComputing/iexec-server-js-client.git"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/iExecBlockchainComputing/iexec-server-js-client/issues"
18 | },
19 | "homepage": "https://github.com/iExecBlockchainComputing/iexec-server-js-client#readme",
20 | "devDependencies": {
21 | "babel-cli": "^6.26.0",
22 | "babel-plugin-transform-runtime": "^6.23.0",
23 | "babel-preset-env": "1.7.0",
24 | "eslint": "5.6.1",
25 | "eslint-config-airbnb-base": "13.1.0",
26 | "eslint-plugin-import": "2.14.0",
27 | "jest": "23.6.0"
28 | },
29 | "dependencies": {
30 | "babel-runtime": "^6.26.0",
31 | "cross-fetch": "2.2.2",
32 | "debug": "4.1.0",
33 | "dev-null": "^0.1.1",
34 | "jsontoxml": "1.0.1",
35 | "qs": "^6.5.2",
36 | "through2": "^2.0.3",
37 | "uuid": "3.3.2",
38 | "xml2js-es6-promise": "^1.1.1"
39 | },
40 | "babel": {
41 | "plugins": [
42 | "transform-runtime"
43 | ],
44 | "presets": [
45 | [
46 | "env",
47 | {
48 | "targets": {
49 | "firefox": "21"
50 | }
51 | }
52 | ]
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/iexec-server-js-client.js:
--------------------------------------------------------------------------------
1 | const Debug = require('debug');
2 | const uuidV4 = require('uuid/v4');
3 | const xml2js = require('xml2js-es6-promise');
4 | const json2xml = require('jsontoxml');
5 | const fetch = require('cross-fetch');
6 | const qs = require('qs');
7 | const devnull = require('dev-null');
8 | const through2 = require('through2');
9 | const utils = require('./utils');
10 | const { getAppBinaryFieldName, waitFor } = require('./utils');
11 |
12 | const debug = Debug('iexec-server-js-client');
13 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
14 |
15 | const createIEXECClient = ({
16 | login = '',
17 | password = '',
18 | server = '',
19 | jwt = '',
20 | mandated = '',
21 | cookie = '',
22 | authURL = 'https://auth.iex.ec',
23 | }) => {
24 | debug('server', server);
25 | const BASICAUTH_CREDENTIALS = login
26 | ? Buffer.from(login.concat(':', password)).toString('base64')
27 | : undefined;
28 |
29 | const STATE_AUTH = cookie ? { state: cookie } : {};
30 | let mandatedLogin = mandated;
31 | const APPS = {};
32 | const hostname = () => {
33 | if (server) return server.split('://')[1].split(':')[0];
34 | return '';
35 | };
36 | const uri2uid = uri => uri.split(`xw://${hostname()}/`)[1];
37 | const uid2uri = uid => `xw://${hostname()}/${uid}`;
38 |
39 | const xmlFormat = async (res) => {
40 | const xmlResponse = await res.text();
41 | debug('xmlResponse', xmlResponse);
42 | const jsResponse = await xml2js(xmlResponse);
43 | debug('jsResponse', jsResponse);
44 | return jsResponse;
45 | };
46 | const streamFormat = res => res;
47 | const textFormat = res => res.text();
48 |
49 | const resFormatPresets = {
50 | xml: xmlFormat,
51 | stream: streamFormat,
52 | text: textFormat,
53 | };
54 |
55 | const http = method => async (
56 | endpoint,
57 | {
58 | uid = '', params = {}, body = undefined, format = xmlFormat,
59 | } = {},
60 | ) => {
61 | try {
62 | const MANDATED = mandatedLogin !== '' ? { XWMANDATINGLOGIN: mandatedLogin } : {};
63 | const allParams = Object.assign({}, params, STATE_AUTH, MANDATED);
64 | const queryString = Object.keys(allParams).length !== 0 ? '?'.concat(qs.stringify(allParams)) : '';
65 | const uri = server.concat('/', endpoint, uid ? '/' : '', uid, queryString);
66 | const headers = BASICAUTH_CREDENTIALS
67 | ? { Authorization: 'Basic '.concat(BASICAUTH_CREDENTIALS) }
68 | : {};
69 |
70 | debug(method, uri);
71 | const res = await fetch(uri, {
72 | method,
73 | body,
74 | headers,
75 | });
76 | const formatFn = typeof format === 'string' ? resFormatPresets[format] : format;
77 | return formatFn(res);
78 | } catch (error) {
79 | debug('http', error);
80 | throw error;
81 | }
82 | };
83 | const get = http('GET');
84 | const post = http('POST');
85 |
86 | const getCookieByJWT = async (jwtoken) => {
87 | try {
88 | const authCookie = await get('ethauth/', {
89 | params: { ethauthtoken: jwtoken, noredirect: 'true' },
90 | format: streamFormat,
91 | }).then(res => res.text());
92 | debug('authCookie', authCookie);
93 | STATE_AUTH.state = authCookie;
94 | return authCookie;
95 | } catch (error) {
96 | debug('getCookieByJWT()', error);
97 | throw Error;
98 | }
99 | };
100 |
101 | function setMandated(mandatedUser) {
102 | mandatedLogin = mandatedUser;
103 | }
104 |
105 | const getByUID = uid => get('get', { uid });
106 | const removeByUID = uid => get('remove', { uid });
107 | const removeUID = (uid) => {
108 | console.log('deprecated, use removeByUID() instead');
109 | return removeByUID(uid);
110 | };
111 | const getAppByName = uid => get('getappbyname', { uid });
112 | const getAppsUIDs = () => get('getapps').then(uids => uids.xwhep.XMLVector[0].XMLVALUE.map(e => e.$.value));
113 | const getAppsByUIDs = appsUIDs => Promise.all(appsUIDs.map(uid => getByUID(uid)));
114 | const getWorkByExternalID = uid => get('getworkbyexternalid', { uid });
115 | const sendData = xmlData => get('senddata', { params: { XMLDESC: xmlData } });
116 | const sendApp = xmlApp => get('sendapp', { params: { XMLDESC: xmlApp } });
117 | const sendWork = xmlWork => get('sendwork', { params: { XMLDESC: xmlWork } });
118 | const download = (uid, options) => get('downloaddata', Object.assign({ uid }, options));
119 |
120 | const createDownloadURI = workResultURI => server.concat(`/downloaddata/${uri2uid(workResultURI)}?state=${STATE_AUTH.state}`);
121 |
122 | const defaultApp = { accessrights: '0x1700', type: 'DEPLOYABLE' };
123 | const defaultWork = { accessrights: '0x1700', status: 'UNAVAILABLE' };
124 | const createXMLApp = app => `${json2xml(Object.assign(defaultApp, app))}`;
125 | const createXMLWork = work => `${json2xml(Object.assign(defaultWork, work))}`;
126 |
127 | const registerApp = async (appParams = {}) => {
128 | const appUID = uuidV4();
129 | debug('appUID', appUID);
130 | await sendApp(createXMLApp(Object.assign({ uid: appUID }, appParams)));
131 | return appUID;
132 | };
133 |
134 | const submitWork = async (appUID, params = {}) => {
135 | const workUID = uuidV4();
136 | debug('workUID', workUID);
137 | await sendWork(createXMLWork(Object.assign(params, { uid: workUID, appuid: appUID })));
138 | await sendWork(createXMLWork(Object.assign(params, { uid: workUID, appuid: appUID })));
139 | const work = await getByUID(workUID);
140 | debug('work.xwhep.work[0].status[0]', work.xwhep.work[0].status[0]);
141 | work.xwhep.work[0].status[0] = 'PENDING';
142 | await sendWork(json2xml(work));
143 | return workUID;
144 | };
145 |
146 | const waitForWorkCompleted = async workUID => waitFor(getByUID, workUID);
147 |
148 | const appsToCache = apps => apps.forEach((app) => {
149 | const appUID = app.xwhep.app[0].uid[0];
150 | APPS[app.xwhep.app[0].name[0]] = appUID;
151 | });
152 |
153 | const updateAppsCache = async () => {
154 | const appsUIDs = await getAppsUIDs();
155 | const cacheAppsUIDs = Object.keys(APPS).map(name => APPS[name]);
156 | debug('cacheAppsUIDs', cacheAppsUIDs);
157 | const notCachedUIDs = appsUIDs.filter(uid => !cacheAppsUIDs.includes(uid));
158 | debug('notCachedUIDs', notCachedUIDs);
159 | const notCachedApps = await getAppsByUIDs(notCachedUIDs);
160 | appsToCache(notCachedApps);
161 | };
162 |
163 | const submitWorkByAppName = async (appName, params = {}) => {
164 | const app = await getAppByName(appName);
165 | const appUID = utils.getFieldValue(app, 'uid');
166 | debug('appUID', appUID, 'from name', appName);
167 | return submitWork(appUID, params);
168 | };
169 |
170 | const downloadStream = (uid, stream = '') => new Promise(async (resolve, reject) => {
171 | const res = await get('downloaddata', { uid, format: streamFormat });
172 |
173 | let buff = Buffer.from('', 'utf8');
174 | let full = false;
175 | const bufferSize = 1 * 1024;
176 | const outputStream = stream === '' ? devnull() : stream;
177 |
178 | res.body
179 | .pipe(
180 | through2((chunk, enc, cb) => {
181 | if (!full) {
182 | buff = Buffer.concat([buff, chunk]);
183 | if (buff.length >= bufferSize) {
184 | debug('Buffer limit reached', buff.length);
185 | full = true;
186 | }
187 | }
188 | cb(null, chunk);
189 | }),
190 | )
191 | .on('error', reject)
192 | .pipe(outputStream)
193 | .on('error', reject)
194 | .on('finish', () => {
195 | debug('finish event');
196 | debug('buff.length', buff.length);
197 | debug('buff.slice(0, bufferSize).length', buff.slice(0, bufferSize).length);
198 | resolve({ stdout: buff.slice(0, bufferSize).toString() });
199 | });
200 | });
201 |
202 | const init = async () => {
203 | if (jwt) await getCookieByJWT(jwt);
204 | await updateAppsCache();
205 | };
206 |
207 | const getTypedMessage = () => fetch(`${authURL}/typedmessage`).then(res => res.json());
208 | const getJWTBySignature = (msgJSON, address, signResult, { type = 'typedauth' } = {}) => fetch(`${authURL}/${type}?message=${msgJSON}&address=${address}&signature=${signResult}`).then(
209 | res => res.json(),
210 | );
211 |
212 | return Object.assign(
213 | {
214 | server,
215 | state: STATE_AUTH,
216 | init,
217 | get,
218 | post,
219 | getCookieByJWT,
220 | setMandated,
221 | getByUID,
222 | removeByUID,
223 | removeUID,
224 | getAppByName,
225 | getAppsUIDs,
226 | getAppsByUIDs,
227 | appsToCache,
228 | updateAppsCache,
229 | getWorkByExternalID,
230 | sendWork,
231 | sendData,
232 | sendApp,
233 | download,
234 | downloadStream,
235 | createDownloadURI,
236 | registerApp,
237 | submitWork,
238 | submitWorkByAppName,
239 | waitForWorkCompleted,
240 | uri2uid,
241 | uid2uri,
242 | getAppBinaryFieldName,
243 | getTypedMessage,
244 | getJWTBySignature,
245 | },
246 | utils,
247 | );
248 | };
249 | module.exports = createIEXECClient;
250 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const Debug = require('debug');
2 |
3 | const debug = Debug('iexec-server-js-client:utils');
4 |
5 | const getAppBinaryFieldName = (_os, _cpu) => {
6 | if (_os === undefined || _cpu === undefined) {
7 | throw new Error('OS or CPU undefined');
8 | }
9 |
10 | const os = _os.toUpperCase();
11 | const cpu = _cpu.toUpperCase();
12 |
13 | if (os === 'JAVA') {
14 | return 'javauri';
15 | }
16 |
17 | switch (os) {
18 | case 'LINUX':
19 | switch (cpu) {
20 | case 'IX86':
21 | return 'linux_ix86uri';
22 | case 'PPC':
23 | return 'linux_ppcuri';
24 | case 'AMD64':
25 | return 'linux_amd64uri';
26 | case 'X86_64':
27 | return 'linux_x86_64uri';
28 | case 'IA64':
29 | return 'linux_ia64uri';
30 | default:
31 | break;
32 | }
33 | break;
34 | case 'WIN32':
35 | switch (cpu) {
36 | case 'IX86':
37 | return 'win32_ix86uri';
38 | case 'AMD64':
39 | return 'win32_amd64uri';
40 | case 'X86_64':
41 | return 'win32_x86_64uri';
42 | default:
43 | break;
44 | }
45 | break;
46 | case 'MACOSX':
47 | switch (cpu) {
48 | case 'IX86':
49 | return 'macos_ix86uri';
50 | case 'X86_64':
51 | return 'macos_x86_64uri';
52 | case 'PPC':
53 | return 'macos_ppcuri';
54 | default:
55 | break;
56 | }
57 | break;
58 | default:
59 | break;
60 | }
61 | return undefined;
62 | };
63 |
64 | const getFieldValue = (obj, field) => {
65 | const [objName] = Object.keys(obj.xwhep);
66 | const fields = Object.keys(obj.xwhep[objName][0]);
67 | if (!fields.includes(field)) throw Error(`getFieldValue() no ${field} in ${objName}`);
68 | return obj.xwhep[objName][0][field][0];
69 | };
70 |
71 | const FETCH_INTERVAL = 5000;
72 | const sleep = ms => new Promise(res => setTimeout(res, ms));
73 |
74 | const waitFor = async (fn, uid, counter = 0) => {
75 | try {
76 | const work = await fn(uid);
77 | debug('waitFor()', counter, uid, 'status', work.xwhep.work[0].status[0]);
78 | const status = getFieldValue(work, 'status');
79 | if (status === 'COMPLETED') return work;
80 | if (status === 'ERROR') throw Error('Work status = ERROR');
81 | await sleep(FETCH_INTERVAL);
82 | return waitFor(fn, uid, counter + 1);
83 | } catch (error) {
84 | debug('waitFor()', uid, error);
85 | throw error;
86 | }
87 | };
88 |
89 | const waitForWorkResult = async (fn, txHash, counter = 0) => {
90 | const workResult = await fn(txHash);
91 |
92 | debug('counter', counter);
93 | debug('workResult', workResult);
94 | const status = workResult.status.toNumber();
95 | if (status === 4) return workResult.uri;
96 | if (status === 5) throw Error('Bridge computation failed');
97 |
98 | await sleep(FETCH_INTERVAL);
99 | return waitForWorkResult(fn, txHash, counter + 1);
100 | };
101 |
102 | module.exports = {
103 | getAppBinaryFieldName,
104 | getFieldValue,
105 | waitFor,
106 | waitForWorkResult,
107 | };
108 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | "env": {
2 | "jest": true
3 | }
4 |
--------------------------------------------------------------------------------
/test/Echo:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo $@
4 |
--------------------------------------------------------------------------------
/test/iexec-server-js-client.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const xml2js = require('xml2js-es6-promise');
3 | const createIEXECClient = require('../src/iexec-server-js-client');
4 |
5 | const {
6 | XW_LOGIN, XW_PWD, XW_SERVER, JWTOKEN,
7 | } = process.env;
8 | const login = '' || XW_LOGIN;
9 | const password = '' || XW_PWD;
10 | const server = '' || XW_SERVER;
11 | const jwtoken = '' || JWTOKEN;
12 | const iexec = createIEXECClient({ login, password, server });
13 |
14 | test('registerApp() & submitWorkByAppName()', async () => {
15 | expect.assertions(2);
16 | const data = fs.readFileSync('./test/Echo');
17 | const { size } = fs.statSync('./test/Echo');
18 | const appUID = await iexec.registerApp(data, size, {
19 | name: 'Echo',
20 | type: 'BINARY',
21 | cpu: 'AMD64',
22 | os: 'LINUX',
23 | }, { name: 'Echo' });
24 | expect(appUID).toBeTruthy();
25 | const workUID = await iexec.submitWorkByAppName('Echo', { cmdline: 'Hello World' });
26 | expect(workUID).toBeTruthy();
27 | }, 15000);
28 |
29 | test('getCookieByJWT()', async () => expect(iexec.getCookieByJWT(jwtoken)).resolves.toBeTruthy());
30 |
31 | const dataXML = '9d2e2ae0-be3b-4bb0-ad45-e7e4aeb0bc4ea98c4179-c756-4678-839d-eeae6ca36f6f0x755fileName02017-12-18 10:01:06ERRORBINARYAMD64LINUXxw://xw.iex.ec/9d2e2ae0-be3b-4bb0-ad45-e7e4aeb0bc4efalsefalse';
32 | test('getFieldValue(dataXML)', async () => {
33 | const data = await xml2js(dataXML);
34 | expect(iexec.getFieldValue(data, 'uid')).toBe('9d2e2ae0-be3b-4bb0-ad45-e7e4aeb0bc4e');
35 | expect(iexec.getFieldValue(data, 'status')).toBe('ERROR');
36 | expect(iexec.getFieldValue(data, 'uri')).toBe('xw://xw.iex.ec/9d2e2ae0-be3b-4bb0-ad45-e7e4aeb0bc4e');
37 | });
38 |
39 | const appXML = '18bb115a-8740-46f2-b907-3aab499de920839bad45-893c-4196-a11f-924c04352ce40x7550xe0536a1e27e069a379462a110555154dbccd102efalseDEPLOYABLE01600500100xw://xw.iex.ec/47a7a8f2-fa50-4f20-a0cf-ea6ff778c7c4';
40 | test('getFieldValue(appXML)', async () => {
41 | const app = await xml2js(appXML);
42 | expect(iexec.getFieldValue(app, 'uid')).toBe('18bb115a-8740-46f2-b907-3aab499de920');
43 | expect(iexec.getFieldValue(app, 'accessrights')).toBe('0x755');
44 | expect(iexec.getFieldValue(app, 'linux_amd64uri')).toBe('xw://xw.iex.ec/47a7a8f2-fa50-4f20-a0cf-ea6ff778c7c4');
45 | });
46 |
47 | const uid = '47a7a8f2-fa50-4f20-a0cf-ea6ff778c7c4';
48 | test('uri2uid()', () => expect(iexec.uri2uid(iexec.uid2uri(uid))).toBe(uid));
49 |
--------------------------------------------------------------------------------