UNPKG

@nativedocuments/docx-wasm

Version:

Convert Microsoft Word documents (docx or binary .doc) to PDF; and doc to docx.

780 lines (757 loc) 40.5 kB
const http = require('https'); const url = require('url'); const docx_formats = require("./formats"); const docx_errors = require("./errors"); const docx_log = require("./log"); var nd = {}; var _cached_api = null; var _init_options = null; var _pending_jobs = []; const ND_AUTH_URL = "https://devauth.nativedocument.com/token?api=5D127559"; function _nd_api_load(f, resolve, reject, options) { if (typeof f === 'string' || f instanceof String) { f = this['readBinary'](f); } // console.log("_nd_api_load: "+f.length); this._noox_worker_main_reset_buffer(); var b = this._noox_worker_main_grow_buffer(f.length); this.HEAPU8.set(f, b); this._noox_worker_main_commit_buffer(f.length); // console.log("Importing..."); try { this._noox_worker_main_reset_writer(); if (options) { // pass options to _noox_worker_main_import_file const s = options ? JSON.stringify(options) : ""; const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = this._noox_worker_main_ensure_writer_length(dst_size); this.stringToUTF8(s, dst, dst_size); this._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); } if (0 === this._noox_worker_main_import_file()) { resolve(); } else { reject(new docx_errors.OperationFailedError("couldn't load. Is this a doc/docx?")); } } catch (e) { // in case of abort //console.error(e); reject(new docx_errors.EngineExceptionError("couldn't load. Is this a doc/docx?", this)); } this._noox_worker_main_reset_buffer(); } function _nd_api_raw(f, resolve, reject) { this._noox_worker_main_raw_import(0, 0); var b = this._noox_worker_main_grow_raw(f.length); this.HEAPU8.set(f, b); try { const base_ts = this._noox_worker_main_raw_import(f.length, 0); if (base_ts > 0) { resolve(base_ts); } else { reject(new docx_errors.OperationFailedError("raw import failed")); } } catch (e) { // in case of abort //console.error(e); this._noox_worker_main_raw_import(-1, 0); reject(new docx_errors.EngineExceptionError("raw import failed", this)); } } function _nd_api_invoke_export_worker(noox_worker_fct, msg) { return new Promise((resolve, reject) => { this._noox_worker_main_reset_buffer(); try { if (0 === noox_worker_fct()) { resolve(); } else { reject(new OperationFailedError(msg)); } } catch (e) { reject(new docx_errors.EngineExceptionError("export failed.", this)); } }); } function _nd_api_get_export_token(needs_export_token) { return new Promise((resolve, reject) => { if (needs_export_token && this._noox_worker_main_needs_export_token()) { const token_ts = this._noox_worker_main_get_export_token_ts(); const post_data = JSON.stringify({ token_ts: token_ts, dev_key: _init_options.ND_DEV_ID, dev_secret: _init_options.ND_DEV_SECRET }); const POST_OPTIONS = { agent: _init_options.ND_AUTH_AGENT, host: _init_options.ND_AUTH_URL.hostname, port: _init_options.ND_AUTH_URL.port, path: _init_options.ND_AUTH_URL.path, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(post_data) } }; var resp = { data: null }; var post_req = http.request(POST_OPTIONS, (res) => { if (res.statusCode < 200 || res.statusCode > 299) { reject(new docx_errors.TokenValidationError("Error validating key " + _init_options.ND_DEV_ID + " with secret " + _init_options.ND_DEV_SECRET)); } else { res.setEncoding('utf8'); res.on('data', function (chunk) { resp.data = (null === resp.data ? chunk : resp.data + chunk); }); res.on('end', () => { try { const body = JSON.parse(resp.data); this._noox_worker_main_set_export_token(body.token || 0); resolve(); } catch (e) { reject(new docx_errors.TokenValidationError("Error validating key " + _init_options.ND_DEV_ID + " with secret " + _init_options.ND_DEV_SECRET)); } }); res.on('error', (e) => { reject(new docx_errors.TokenValidationError(e)); }); } }); post_req.write(post_data); post_req.on('error', (e) => { reject(new docx_errors.NetworkError("Network error while fetching token: " + e.message, e.stack.toString())); }); post_req.end(); //@TODO maybe reusing, keeping alive for this connection would really speed things up? } else { //console.log("NO TOKEN NEEDED"); resolve(); } }); } function _nd_api_export_template(noox_worker_fct, msg, needs_export_token, as_string) { return new Promise((resolve_export, reject_export) => { Promise.all([ _nd_api_get_export_token.call(this, needs_export_token), _nd_api_invoke_export_worker.call(this, noox_worker_fct, msg) ]).then(() => { var buffer = this._noox_worker_main_get_buffer(); var buffer_len = this._noox_worker_main_get_buffer_length(); var data = null; if (true === as_string) { data = this.UTF8ToString(buffer, buffer_len); } else if ("function" === typeof as_string) { data = as_string(buffer, buffer_len); } else { data = new ArrayBuffer(buffer_len); { // copy var helper = this.HEAPU8.slice(buffer, buffer + buffer_len); var data8 = new Uint8Array(data); data8.set(helper, 0); } } this._noox_worker_main_reset_buffer(); resolve_export(data); }).catch((error) => { this._noox_worker_main_reset_buffer(); reject_export(error); }); }); } nd['init'] = function (options) { options = options || {}; _init_options = Object.assign({}, { ENVIRONMENT: "NODE", ND_AUTH_URL: url.parse(process.env.ND_AUTH_URL || ND_AUTH_URL), ND_AUTH_AGENT: new http.Agent({ keepAlive: true }), ND_DEV_ID: process.env.ND_DEV_ID, ND_DEV_SECRET: process.env.ND_DEV_SECRET, ND_LICENSE_URL: process.env.ND_LICENSE_URL, ND_MAX_STACK_SIZE_MB: options.ND_MAX_STACK_SIZE_MB || 5, ND_MIN_HEAP_SIZE_MB: options.ND_MIN_HEAP_SIZE_MB || (256 - 5), ND_MIN_STREAM_SIZE_MB: options.ND_MIN_STREAM_SIZE_MB || 256, _ND_FONTPOOL_SIZE: options._ND_FONTPOOL_SIZE || 0, ND_EXTERNALS: options.ND_EXTERNALS, ND_RESOURCES: options.ND_RESOURCES }, options); if (!_init_options.ND_LICENSE_URL && (!_init_options.ND_DEV_ID || 26 !== _init_options.ND_DEV_ID.length)) { if (!_init_options.ND_DEV_ID || 0 === _init_options.ND_DEV_ID.length) { throw new docx_errors.InitFailedError("ND_DEV_ID is missing. To use this module, you need a ND_DEV_ID and ND_DEV_SECRET. Get yours at https://developers.nativedocuments.com then pass these in as ENV variables, or in your nd.init config object."); } else { throw new docx_errors.InitFailedError("Invalid ND_DEV_ID."); } } if (!_init_options.ND_LICENSE_URL && (!_init_options.ND_DEV_SECRET || 26 !== _init_options.ND_DEV_SECRET.length)) { throw new docx_errors.InitFailedError("Invalid ND_DEV_SECRET."); } return new Promise(function (resolve, reject) { if (_init_options && false === _init_options.LAZY_INIT) { return nd.engine().then(function (api) { resolve(api); api.close(); }).catch(function (e) { reject(e); }); } else { resolve(null); } }); }; function resourceLookupHelper(module, packagename, filename) { var ret = null; var res; if (module && (res = module["ND_RESOURCES"])) { ret = res[packagename]; if (ret) { if ("function" === typeof ret) { ret = ret(filename); } else { ret = ret[filename]; } } } return ret || null; } function ensureEngine() { return new Promise(function (resolve, reject) { if (null !== _cached_api && Array.isArray(_cached_api)) { // pending init, store resolve and reject to call them later _cached_api.push(resolve); _cached_api.push(reject); } else if (null !== _cached_api && _cached_api['isSane']()) { // no pending request, but valid engine resolve(_cached_api); // cache hit } else { // no cache if (null !== _init_options) { //console.debug("Initializing engine..."); _cached_api = []; // signal init is pending var nd_worker = require("./noox_worker.js"); nd_worker(Object.assign({}, _init_options, { TOTAL_STACK: _init_options.ND_MAX_STACK_SIZE_MB * 1024 * 1024, // looks like 1536 is max TOTAL_MEMORY for nodejs TOTAL_MEMORY: (_init_options.ND_MAX_STACK_SIZE_MB + _init_options.ND_MIN_HEAP_SIZE_MB + _init_options.ND_MIN_STREAM_SIZE_MB) * 1024 * 1024, WASM_ENV: { NDS_MIN_STREAM_SIZE: _init_options.ND_MIN_STREAM_SIZE_MB, NDS_VERBOSE: ('number' === typeof _init_options.ND_VERBOSE ? _init_options.ND_VERBOSE : undefined), NOOX_FONTPOOL_SIZE_OVERRIDE: _init_options._ND_FONTPOOL_SIZE || undefined, NOOX_FORCE_DELETIONS_INLINE: _init_options._ND_DELETIONS_INLINE ? 1 : 0 }, wasmBinary: _init_options.WASM_BINARY || resourceLookupHelper(_init_options, "@nativedocuments/docx-wasm", "noox_worker.wasm"), arguments: [], console: _init_options.console || (typeof self === 'object' ? self["console"] : undefined) || (typeof window === 'object' ? window["console"] : undefined) || (typeof console === 'object' ? console : undefined) || undefined, ndSyslog: function (level, msg) { if (this && this['WASM_ENV'] && undefined !== this['WASM_ENV']['NDS_VERBOSE'] && this['console']) { switch (level) { case 0: case 1: if (this['console']['error']) { this['console']['error'](msg); } break; case 3: if (this['console']['warn']) { this['console']['warn'](msg); } break; case 2: case 4: if (this['console']['log']) { this['console']['log'](msg); } break; default: if (this['console']['debug']) { this['console']['debug'](msg); } break; } } if (this && this['ndLog']) { this['ndLog'].push(level, msg); } }, ndLog: new docx_log() })).then(function (module) { // console.log("HAS RESOURCES(POST)="+JSON.stringify(Object.keys(module["ndResources"]))); module["wasmBinary"] = null; // // "free", i.e. remove reference to wasmBinary, so GC can free it var nd_api = {}; nd_api['isSane'] = function () { return module && !module.isTerm(); }; nd_api['busy'] = false; nd_api['close'] = function () { // close api return new Promise(function (resolve, reject) { //@TODO close document nd_api['busy'] = false; resolve(); if (_pending_jobs.length > 0) { const eninge_resolve = _pending_jobs.shift(); nd_api['busy'] = true; eninge_resolve(nd_api); } }); }; nd_api['load'] = function (f, options) { return new Promise(function (resolve, reject) { if (module && !module.isTerm()) { _nd_api_load.call(module, f, resolve, reject, options); } else { reject(new docx_errors.EngineTerminatedError()); } }); }; nd_api['raw'] = function (f) { return new Promise(function (resolve, reject) { if (module && !module.isTerm()) { _nd_api_raw.call(module, f, resolve, reject); } else { reject(new docx_errors.EngineTerminatedError()); } }); }; nd_api['exportPDF'] = function () { if (module && !module.isTerm()) { return _nd_api_export_template.call(module, module._noox_worker_main_export_as_pdf.bind(module, 0), "exportPDF", true); } else { reject(new docx_errors.EngineTerminatedError()); } }; nd_api['exportDOCX'] = function (ranges) { if (module && !module.isTerm()) { module._noox_worker_main_reset_writer(); if (ranges && module.ndRawTextToken) { var dst_ofs = module._noox_worker_main_commit_writer_length(0); if (module.ndRawTextToken) { const dst_size = (module.ndRawTextToken.length + 1) * 4; const dst = module._noox_worker_main_ensure_writer_length(dst_ofs + dst_size); module.stringToUTF8(module.ndRawTextToken, dst + dst_ofs, dst_size); dst_ofs = module._noox_worker_main_commit_writer_length(-dst_size /* - for zero termination */); module.HEAPU8[module._noox_worker_main_ensure_writer_length(1) + dst_ofs] = 10; dst_ofs = module._noox_worker_main_commit_writer_length(1); } if (ranges) { Object.keys(ranges).forEach(function (key) { const line = [JSON.stringify(key), JSON.stringify(ranges[key])].join(":"); const dst_size = (line.length + 1) * 4; const dst = module._noox_worker_main_ensure_writer_length(dst_ofs + dst_size); module.stringToUTF8(line, dst + dst_ofs, dst_size); dst_ofs = module._noox_worker_main_commit_writer_length(-dst_size /* - for zero termination */); module.HEAPU8[module._noox_worker_main_ensure_writer_length(1) + dst_ofs] = 10; dst_ofs = module._noox_worker_main_commit_writer_length(1); }); } const failed = module._noox_worker_main_import_ranges(); module._noox_worker_main_reset_writer(); if (failed) { throw new docx_errors.OperationFailedError("invalid ranges"); } } const ret = _nd_api_export_template.call(module, module._noox_worker_main_export_as_docx.bind(module, ranges ? 1 : 0), "exportDOCX", true); if (ranges) { module._noox_worker_main_reset_writer(); module._noox_worker_main_import_ranges(); } return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['exportRawText'] = function () { if (module && !module.isTerm()) { return _nd_api_export_template.call(module, module._noox_worker_main_export_as_raw_text.bind(module), "exportRawText", true, function (buffer, buffer_len) { var i = 0; while (i < buffer_len && this.HEAPU8[buffer + i] >= 32) i++; this.HEAPU8[buffer + i] = 0; module.ndRawTextToken = this.UTF8ToString(buffer, i); while (i < buffer_len && this.HEAPU8[buffer + i] < 32) i++; return this.UTF8ToString(buffer + i, buffer_len - i); }.bind(module)); } else { reject(new docx_errors.EngineTerminatedError()); } }; nd_api['exportRawJSON'] = function (multiline_json) { if (module && !module.isTerm()) { return _nd_api_export_template.call(module, module._noox_worker_main_export_as_raw_json.bind(module, multiline_json), "exportRawJSON", true, function (buffer, buffer_len) { var ret = null; try { ret = JSON.parse(this.UTF8ToString(buffer, buffer_len)); } catch (e) { } return ret; }.bind(module)); } else { reject(new docx_errors.EngineTerminatedError()); } }; nd_api['updateLayout'] = function () { if (module && !module.isTerm()) { return module._noox_worker_update_layout(); } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getLayoutInfo'] = function () { var ret = null; if (module && !module.isTerm()) { module._noox_worker_main_reset_writer(); const layoutStable = module._noox_worker_get_layout_info(); try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { // error } module._noox_worker_main_reset_writer(); } else { throw new docx_errors.EngineTerminatedError(); } return ret; }; nd_api['getPagesInfo'] = function (delta) { var ret = null; if (module && !module.isTerm()) { module._noox_worker_main_reset_writer(); if (0 === module._noox_worker_get_pages_info(delta ? 1 : 0)) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { // error } } else { // error } module._noox_worker_main_reset_writer(); } else { throw new docx_errors.EngineTerminatedError(); } return ret; }; nd_api['getPage'] = function (page_no) { var ret = null; if (module && !module.isTerm()) { module._noox_worker_main_reset_writer(); if (0 === module._noox_worker_get_page_as_json(page_no)) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { // error } } else { // error } module._noox_worker_main_reset_writer(); } else { throw new docx_errors.EngineTerminatedError(); } return ret; }; nd_api['getFont'] = function (f) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_buffer(); module._noox_worker_main_reset_writer(); if (0 === module._noox_worker_main_export_font(f, 1)) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); var buffer = module._noox_worker_main_get_buffer(); var buffer_len = module._noox_worker_main_get_buffer_length(); //console.log("FONT=="+JSON.stringify(ret)+" "+JSON.stringify(buffer_len)); var fontData = new ArrayBuffer(buffer_len); { // copy var helper = module.HEAPU8.slice(buffer, buffer + buffer_len); var data8 = new Uint8Array(fontData); data8.set(helper, 0); } ret.fontData = fontData; ret.contentType = module.UTF8ToString(module._noox_worker_main_mimetype_buffer()); module._noox_worker_main_reset_buffer(); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_buffer(); module._noox_worker_main_reset_writer(); // error return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getImage'] = function (image_so, w, h) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_buffer(); if (0 === module._noox_worker_main_export_image(image_so, w, h)) { try { var buffer = module._noox_worker_main_get_buffer(); var buffer_len = module._noox_worker_main_get_buffer_length(); //console.log("IMAGE=="+JSON.stringify(ret)+" "+JSON.stringify(buffer_len)); var imageData = new ArrayBuffer(buffer_len); { // copy var helper = module.HEAPU8.slice(buffer, buffer + buffer_len); var data8 = new Uint8Array(imageData); data8.set(helper, 0); } ret = { imageData: imageData, contentType: module.UTF8ToString(module._noox_worker_main_mimetype_buffer()) } module._noox_worker_main_reset_buffer(); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_buffer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getStyle'] = function (s) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); module.stringToUTF8(s, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); if (0 === module._noox_worker_main_resolve_style_id()) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getStyleSheet'] = function () { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); if (0 === module._noox_worker_main_resolve_style_id()) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getSession'] = function (so) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); if (0 === module._noox_worker_main_get_session(so)) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['performOperations'] = function (s) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); // a string ID never contains complex UTF-8 chars module.stringToUTF8(s, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); if (0 === module._noox_worker_main_perform_operation()) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['fetchSyncMessage'] = function (connect, options) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); if (options) { const s = JSON.stringify(options); const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); module.stringToUTF8(s, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); } const again = module._noox_worker_fetch_sync_message(connect ? 1 : 0); try { const l = module._noox_worker_main_get_writer_length(); const b = module._noox_worker_main_get_writer(); ret = module.HEAPU8.slice(b, b + l); } catch (e) { console.error(e); // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['performSyncMessage'] = function (data) { if (module && !module.isTerm()) { module._noox_worker_main_reset_sync(); const data_size = data.byteLength; const dst = module._noox_worker_main_ensure_sync_length(data_size); module.HEAPU8.set(data, dst); var ret = module._noox_worker_perform_sync(data_size); if (0 === ret) { if (module._noox_worker_main_get_writer_length() > 0) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } module._noox_worker_main_reset_sync(); return ret; } else if (ret < 0) { var msg; try { msg = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error msg = null; } module._noox_worker_main_reset_sync(); throw new docx_errors.EngineSyncError(msg); } else { module._noox_worker_main_reset_sync(); return ret; } } else { module._noox_worker_main_reset_sync(); throw new docx_errors.EngineTerminatedError(); } }; nd_api['getStatus'] = function (options) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); const s = options ? JSON.stringify(options) : ""; const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); module.stringToUTF8(s, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); if (0 === module._noox_worker_get_status()) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['updateSID'] = function (sid) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); const dst_len = sid.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); module.stringToUTF8(sid, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); if (0 === module._noox_worker_update_sid()) { try { ret = module.UTF8ToString(module._noox_worker_main_get_writer()); } catch (e) { console.error(e); // error ret = null; } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; nd_api['getChanges'] = function (options) { if (module && !module.isTerm()) { var ret = null; module._noox_worker_main_reset_writer(); const s = options ? JSON.stringify(options) : ""; const dst_len = s.length * 4; const dst_size = dst_len + 1; const dst = module._noox_worker_main_ensure_writer_length(dst_size); module.stringToUTF8(s, dst, dst_size); module._noox_worker_main_commit_writer_length(-dst_len /* - for zero termination */); if (0 === module._noox_worker_get_changes()) { try { ret = JSON.parse(module.UTF8ToString(module._noox_worker_main_get_writer())); } catch (e) { console.error(e); // error } } else { // error } module._noox_worker_main_reset_writer(); return ret; } else { throw new docx_errors.EngineTerminatedError(); } }; resolve(nd_api); if (null !== _cached_api && Array.isArray(_cached_api)) { // resolve cache if needed for (var i = 0; i < _cached_api.length; i += 2) { _cached_api[i](nd_api); // resolve } _cached_api = nd_api; } }); } else { reject("init not called"); } } }); }; nd['engine'] = function () { return new Promise(function (resolve, reject) { ensureEngine().then(function (api) { if (api['busy']) { _pending_jobs.push(resolve); } else { api['busy'] = true; resolve(api); } }).catch(function (e) { reject(e); }); }); }; module.exports = nd;