diff --git a/.gitattributes b/.gitattributes index 6b14c57f..0984c995 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,6 +13,9 @@ etcher text .git* text *.html text *.json text +*.cpp text +*.h text +*.gyp text LICENSE text Makefile text *.md text diff --git a/.gitignore b/.gitignore index eb6d57dc..77e89638 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ coverage .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release +build # Dependency directory # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git diff --git a/.travis.yml b/.travis.yml index f251837a..e2be6c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/build/docker/run-command.sh -r "${TARGET_ARCH}" -s "${PWD}" -c "make info && make electron-develop"; else - pip install codespell==1.9.2 awscli; + pip install codespell==1.9.2 awscli cpplint; brew install afsctool jq; make info; travis_wait make electron-develop; diff --git a/Makefile b/Makefile index be1eb594..7547f000 100644 --- a/Makefile +++ b/Makefile @@ -174,40 +174,40 @@ $(BUILD_DIRECTORY): $(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY) mkdir $@ -$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: | $(BUILD_DIRECTORY) - mkdir $@ - -$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: | $(BUILD_DIRECTORY) - mkdir $@ - $(BUILD_OUTPUT_DIRECTORY): | $(BUILD_DIRECTORY) mkdir $@ -$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules: package.json npm-shrinkwrap.json \ - | $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies +$(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \ + | $(BUILD_DIRECTORY) + mkdir $@ + cp -rf src $@ ./scripts/build/dependencies-npm.sh -p \ -r "$(TARGET_ARCH)" \ -v "$(ELECTRON_VERSION)" \ - -x $| \ + -x $@ \ -t electron \ -s "$(TARGET_PLATFORM)" + rm -rf $@/src -$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules: package.json npm-shrinkwrap.json \ - | $(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies +$(BUILD_DIRECTORY)/node-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies: package.json npm-shrinkwrap.json \ + | $(BUILD_DIRECTORY) + mkdir $@ + cp -rf src $@ ./scripts/build/dependencies-npm.sh -p -f \ -r "$(TARGET_ARCH)" \ -v "$(NODE_VERSION)" \ - -x $| \ + -x $@ \ -t node \ -s "$(TARGET_PLATFORM)" + rm -rf $@/src $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(APPLICATION_VERSION)-$(TARGET_ARCH)-app: \ - $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies/node_modules \ + $(BUILD_DIRECTORY)/electron-$(TARGET_PLATFORM)-$(TARGET_ARCH)-dependencies \ | $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY) ./scripts/build/electron-create-resources-app.sh -s . -o $@ \ -v $(APPLICATION_VERSION) \ -f "$(APPLICATION_FILES)" - cp -RLf $< $@ + cp -RLf $ { + return bindings({ + bindings: moduleName, + + /* eslint-disable camelcase */ + + module_root: path.join(__dirname, '..', '..') + + /* eslint-enable camelcase */ + + }); +}; diff --git a/lib/shared/permissions.js b/lib/shared/permissions.js index ab832e5a..a0a05d73 100644 --- a/lib/shared/permissions.js +++ b/lib/shared/permissions.js @@ -17,6 +17,7 @@ 'use strict'; const os = require('os'); +const bindings = require('./bindings'); const Bluebird = require('bluebird'); const childProcess = Bluebird.promisifyAll(require('child_process')); const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt')); @@ -145,22 +146,13 @@ exports.elevateCommand = (command, options) => { const prefixedCommand = _.concat(exports.getEnvironmentCommandPrefix(options.environment), command); if (os.platform() === 'win32') { - const elevator = Bluebird.promisifyAll(require('elevator')); - - return elevator.executeAsync(prefixedCommand, { - hidden: true, - terminating: true, - doNotPushdCurrentDirectory: true, - waitForTermination: true - }).then(() => { + const elevator = Bluebird.promisifyAll(bindings.load('elevator')); + return elevator.elevateAsync(_.concat([ + 'cmd.exe', + '/c' + ], prefixedCommand)).then((results) => { return { - cancelled: false - }; - }).catch({ - code: 'ELEVATE_CANCELLED' - }, () => { - return { - cancelled: true + cancelled: results.cancelled }; }); } diff --git a/package.json b/package.json index c632a883..40b97e0e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "description": "Flash OS images to SD cards & USB drives, safely and easily.", "productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.", "homepage": "https://github.com/resin-io/etcher", + "gypfile": true, "repository": { "type": "git", "url": "git@github.com:resin-io/etcher.git" @@ -16,14 +17,18 @@ "scripts": { "test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R spec && electron-mocha --recursive tests/shared tests/child-writer tests/image-stream -R spec", "sass": "node-sass ./lib/gui/scss/main.scss > ./lib/gui/css/main.css", + "cpplint": "cpplint --recursive src", "jslint": "eslint lib tests scripts bin versionist.conf.js", "sasslint": "sass-lint lib/gui/scss", "htmllint": "node scripts/html-lint.js", "codespell": "codespell.py --skip *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,.DS_Store lib tests docs scripts Makefile *.md LICENSE", - "lint": "npm run jslint && npm run sasslint && npm run codespell && npm run htmllint", + "lint": "npm run jslint && npm run sasslint && npm run cpplint && npm run codespell && npm run htmllint", "changelog": "versionist", "start": "electron lib/start.js", - "preshrinkwrap": "node ./scripts/clean-shrinkwrap.js" + "preshrinkwrap": "node ./scripts/clean-shrinkwrap.js", + "configure": "node-gyp configure", + "build": "node-gyp build", + "install": "node-gyp rebuild" }, "author": "Juan Cruz Viotti ", "license": "Apache-2.0", @@ -61,8 +66,7 @@ } }, "optionalDependencies": { - "electron-installer-debian": "0.5.1", - "elevator": "2.2.3" + "electron-installer-debian": "0.5.1" }, "dependencies": { "angular": "1.6.3", @@ -72,6 +76,7 @@ "angular-seconds-to-date": "^1.0.0", "angular-ui-bootstrap": "^2.5.0", "angular-ui-router": "^0.4.2", + "bindings": "^1.2.1", "bluebird": "^3.0.5", "bootstrap-sass": "^3.3.5", "chalk": "^1.1.3", @@ -86,6 +91,7 @@ "lzma-native": "^1.5.2", "mime-types": "^2.1.15", "mountutils": "^1.0.6", + "nan": "^2.3.5", "node-ipc": "^8.9.2", "node-stream-zip": "^1.3.4", "path-is-inside": "^1.0.2", @@ -120,6 +126,7 @@ "html-angular-validate": "^0.1.9", "mochainon": "^1.0.0", "nock": "^9.0.9", + "node-gyp": "^3.5.0", "node-sass": "^3.8.0", "sass-lint": "^1.10.2", "tmp": "0.0.31", diff --git a/scripts/build/dependencies-npm.sh b/scripts/build/dependencies-npm.sh index ef3972b8..9001afea 100755 --- a/scripts/build/dependencies-npm.sh +++ b/scripts/build/dependencies-npm.sh @@ -129,12 +129,17 @@ if [ -n "$ARGV_PREFIX" ]; then cp "$PWD/npm-shrinkwrap.json" "$ARGV_PREFIX/npm-shrinkwrap.json" fi + if [ -f "$PWD/binding.gyp" ]; then + cp "$PWD/binding.gyp" "$ARGV_PREFIX/binding.gyp" + fi + pushd "$ARGV_PREFIX" run_install popd rm -f "$ARGV_PREFIX/package.json" rm -f "$ARGV_PREFIX/npm-shrinkwrap.json" + rm -f "$ARGV_PREFIX/binding.gyp" else run_install fi diff --git a/scripts/build/docker/Dockerfile-i686 b/scripts/build/docker/Dockerfile-i686 index 607d6202..a2033b5f 100644 --- a/scripts/build/docker/Dockerfile-i686 +++ b/scripts/build/docker/Dockerfile-i686 @@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ RUN npm config set spin=false # Python -RUN pip install codespell==1.9.2 awscli +RUN pip install codespell==1.9.2 awscli cpplint diff --git a/scripts/build/docker/Dockerfile-x86_64 b/scripts/build/docker/Dockerfile-x86_64 index 0170aff6..c69668d1 100644 --- a/scripts/build/docker/Dockerfile-x86_64 +++ b/scripts/build/docker/Dockerfile-x86_64 @@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ RUN npm config set spin=false # Python -RUN pip install codespell==1.9.2 awscli +RUN pip install codespell==1.9.2 awscli cpplint diff --git a/scripts/build/docker/Dockerfile.template b/scripts/build/docker/Dockerfile.template index f3076df7..c77b1555 100644 --- a/scripts/build/docker/Dockerfile.template +++ b/scripts/build/docker/Dockerfile.template @@ -36,4 +36,4 @@ RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \ RUN npm config set spin=false # Python -RUN pip install codespell==1.9.2 awscli +RUN pip install codespell==1.9.2 awscli cpplint diff --git a/src/elevator_init.cpp b/src/elevator_init.cpp new file mode 100644 index 00000000..4ced3f69 --- /dev/null +++ b/src/elevator_init.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "os/elevate.h" +#include "utils/v8utils.h" + +NAN_METHOD(Elevate) { + if (!info[0]->IsArray()) { + return Nan::ThrowError("This function expects an array"); + } + + if (!info[1]->IsFunction()) { + return Nan::ThrowError("Callback must be a function"); + } + + std::vector arguments = + etcher::v8utils::GetArguments(info[0].As()); + v8::Local callback = info[1].As(); + + etcher::ELEVATE_RESULT result = etcher::Elevate( + arguments.front(), + std::vector(arguments.begin() + 1, arguments.end())); + + // Create results object + v8::Isolate *isolate = v8::Isolate::GetCurrent(); + v8::Local results = v8::Object::New(isolate); + + switch (result) { + case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS: + results->Set(v8::String::NewFromUtf8(isolate, "cancelled"), Nan::False()); + YIELD_OBJECT(callback, results); + break; + case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED: + results->Set(v8::String::NewFromUtf8(isolate, "cancelled"), Nan::True()); + YIELD_OBJECT(callback, results); + break; + default: + std::string details = etcher::ElevateResultToString(result); + YIELD_ERROR(callback, details.c_str()); + } +} + +NAN_MODULE_INIT(ElevatorInit) { NAN_SET_FUNCTION("elevate", Elevate); } + +NODE_MODULE(elevator, ElevatorInit) diff --git a/src/os/elevate.cpp b/src/os/elevate.cpp new file mode 100644 index 00000000..18042d3a --- /dev/null +++ b/src/os/elevate.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "os/elevate.h" + +static std::string JoinArguments(std::vector arguments) { + std::ostringstream result; + + std::copy(arguments.begin(), arguments.end(), + std::ostream_iterator(result, " ")); + + return result.str(); +} + +// Make sure to delete the result after you're done +// with it by calling `delete[] result;`. +// See http://stackoverflow.com/a/1201471 +static LPCTSTR ConvertStringToLPCTSTR(const std::string &string) { + char *result = new char[string.size() + 1]; + std::copy(string.begin(), string.end(), result); + result[string.size()] = 0; + return result; +} + +etcher::ELEVATE_RESULT etcher::Elevate(const std::string &command, + std::vector arguments) { + // Initialize the SHELLEXECUTEINFO structure. We zero it out + // in order to be on the safe side, and set cbSize to the size + // of the structure as recommend by MSDN + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759784(v=vs.85).aspx + SHELLEXECUTEINFO shellExecuteInfo; + ZeroMemory(&shellExecuteInfo, sizeof(shellExecuteInfo)); + shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO); + + // Flags that indicate the content and validity of the other structure member. + shellExecuteInfo.fMask = + + // Used to indicate that the hProcess member receives the process handle. + // This handle is typically used to allow an application to find out + // when a process created with ShellExecuteEx terminates. + SEE_MASK_NOCLOSEPROCESS | + + // Wait for the execute operation to complete before returning. + SEE_MASK_NOASYNC | + + // Do not display an error message box if an error occurs. + SEE_MASK_FLAG_NO_UI; + + // The action to be performed. + shellExecuteInfo.lpVerb = TEXT("runas"); + + // Run the file in the background + shellExecuteInfo.nShow = SW_HIDE; + + // Use the current directory as the working directory + shellExecuteInfo.lpDirectory = NULL; + + // Set file and parameters + // We can't just assign the result of `.c_str()`, since + // that pointer is owned by the `std::string` instance, + // and will not be safe after the instance is destroyed. + LPCTSTR file = ConvertStringToLPCTSTR(command); + LPCTSTR argv = ConvertStringToLPCTSTR(JoinArguments(arguments)); + shellExecuteInfo.lpFile = file; + shellExecuteInfo.lpParameters = argv; + + BOOL executeResult = ShellExecuteEx(&shellExecuteInfo); + + delete[] file; + delete[] argv; + + // Finally, let's try to elevate the command + if (!executeResult) { + DWORD executeError = GetLastError(); + + switch (executeError) { + case ERROR_FILE_NOT_FOUND: + return etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND; + case ERROR_DDE_FAIL: + return etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL; + case ERROR_NO_ASSOCIATION: + return etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION; + case ERROR_ACCESS_DENIED: + return etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED; + case ERROR_DLL_NOT_FOUND: + return etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND; + case ERROR_CANCELLED: + return etcher::ELEVATE_RESULT::ELEVATE_CANCELLED; + case ERROR_NOT_ENOUGH_MEMORY: + return etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY; + case ERROR_SHARING_VIOLATION: + return etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION; + default: + return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR; + } + } + + // Since we passed SEE_MASK_NOCLOSEPROCESS, the + // process handle is accessible from hProcess. + if (shellExecuteInfo.hProcess) { + // Wait for the process to exit before continuing. + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx + WaitForSingleObject(shellExecuteInfo.hProcess, INFINITE); + + if (!CloseHandle(shellExecuteInfo.hProcess)) { + return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR; + } + } + + return etcher::ELEVATE_RESULT::ELEVATE_SUCCESS; +} + +std::string +etcher::ElevateResultToString(const etcher::ELEVATE_RESULT &result) { + switch (result) { + case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS: + return "Success"; + case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED: + return "The user cancelled the elevation request"; + case etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND: + return "The specified file was not found"; + case etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND: + return "The specified path was not found"; + case etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL: + return "The Dynamic Data Exchange (DDE) transaction failed"; + case etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION: + return "There is no application associated with the " + "specified file name extension"; + case etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED: + return "Access to the specified file is denied"; + case etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND: + return "One of the library files necessary to run the " + "application can't be found"; + case etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY: + return "There is not enough memory to perform the specified action"; + case etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION: + return "A sharing violation occurred"; + default: + return "Unknown error"; + } +} diff --git a/src/os/elevate.h b/src/os/elevate.h new file mode 100644 index 00000000..f99c93db --- /dev/null +++ b/src/os/elevate.h @@ -0,0 +1,59 @@ +#ifndef SRC_OS_ELEVATE_H_ +#define SRC_OS_ELEVATE_H_ + +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Fix winsock.h redefinition errors +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +// Note that windows.h has to be included before any +// other Windows library to avoid declaration issues +#include +#include + +#include +#include +#include +#include +#include + +namespace etcher { + +enum class ELEVATE_RESULT { + ELEVATE_SUCCESS, + ELEVATE_FILE_NOT_FOUND, + ELEVATE_PATH_NOT_FOUND, + ELEVATE_DDE_FAIL, + ELEVATE_NO_ASSOCIATION, + ELEVATE_ACCESS_DENIED, + ELEVATE_DLL_NOT_FOUND, + ELEVATE_CANCELLED, + ELEVATE_NOT_ENOUGH_MEMORY, + ELEVATE_SHARING_VIOLATION, + ELEVATE_UNKNOWN_ERROR +}; + +ELEVATE_RESULT Elevate(const std::string &command, + std::vector arguments); + +std::string ElevateResultToString(const ELEVATE_RESULT &result); + +} // namespace etcher + +#endif // SRC_OS_ELEVATE_H_ diff --git a/src/utils/v8utils.cpp b/src/utils/v8utils.cpp new file mode 100644 index 00000000..78774895 --- /dev/null +++ b/src/utils/v8utils.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utils/v8utils.h" + +std::vector +etcher::v8utils::GetArguments(v8::Local arguments) { + std::vector result(0); + + for (uint32_t index = 0; index < arguments->Length(); index++) { + std::string argument( + *v8::String::Utf8Value(arguments->Get(index)->ToString())); + result.push_back(argument); + } + + return result; +} diff --git a/src/utils/v8utils.h b/src/utils/v8utils.h new file mode 100644 index 00000000..0cc2091f --- /dev/null +++ b/src/utils/v8utils.h @@ -0,0 +1,54 @@ +#ifndef SRC_UTILS_V8UTILS_H_ +#define SRC_UTILS_V8UTILS_H_ + +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +namespace etcher { +namespace v8utils { +std::vector GetArguments(v8::Local arguments); +} // namespace v8utils +} // namespace etcher + +#define YIELD_ERROR(CALLBACK, ERROR) \ + { \ + v8::Local argv[1] = {Nan::Error((ERROR))}; \ + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 1, \ + argv); \ + } \ + return; + +#define YIELD_OBJECT(CALLBACK, OBJECT) \ + { \ + v8::Local argv[2] = {Nan::Null(), (OBJECT)}; \ + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 2, \ + argv); \ + } \ + return; + +#define YIELD_NOTHING(CALLBACK) \ + Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 0, 0); + +#define NAN_SET_FUNCTION(JSSYMBOL, FUNCTION) \ + Nan::Set(target, Nan::New((JSSYMBOL)).ToLocalChecked(), \ + Nan::GetFunction(Nan::New((FUNCTION))) \ + .ToLocalChecked()); + +#endif // SRC_UTILS_V8UTILS_H_