From 03ff41b1b90b70aa0f750129058dae7d900d7ba0 Mon Sep 17 00:00:00 2001 From: Jefry Date: Mon, 12 Dec 2016 23:38:08 +0700 Subject: [PATCH] [AutoUpdater][OSX] add squirrel auto updater --- .gitignore | 1 + BUILD.gn | 40 ++- nw.gypi | 46 ++++ src/api/_api_features.json | 4 + src/api/auto_updater/api_auto_updater.cc | 127 +++++++++ src/api/auto_updater/api_auto_updater.h | 43 +++ src/api/dispatcher_bindings.cc | 2 + src/api/dispatcher_host.cc | 6 + src/api/nw_auto_updater.idl | 19 ++ src/api/schemas.gni | 1 + src/api/schemas.gypi | 1 + src/browser/auto_updater.cc | 35 +++ src/browser/auto_updater.h | 66 +++++ src/browser/auto_updater_mac.mm | 136 +++++++++ src/resources/api_nw_auto_updater.js | 258 ++++++++++++++++++ src/resources/nw_resources.grd | 1 + test/manual/autoupdate/autoupdate.sh | 33 +++ test/manual/autoupdate/index.html | 57 ++++ .../nwjs.dummy.app/Contents/Info.plist | 50 ++++ .../nwjs.dummy.app/Contents/MacOS/nwjs | Bin 0 -> 24608 bytes .../nwjs.dummy.app/Contents/PkgInfo | 1 + .../Resources/Base.lproj/MainMenu.nib | Bin 0 -> 36915 bytes .../Contents/_CodeSignature/CodeResources | 129 +++++++++ test/manual/autoupdate/package.json | 4 + tools/package_binaries.py | 27 +- 25 files changed, 1076 insertions(+), 11 deletions(-) create mode 100644 src/api/auto_updater/api_auto_updater.cc create mode 100644 src/api/auto_updater/api_auto_updater.h create mode 100644 src/api/nw_auto_updater.idl create mode 100644 src/browser/auto_updater.cc create mode 100644 src/browser/auto_updater.h create mode 100644 src/browser/auto_updater_mac.mm create mode 100644 src/resources/api_nw_auto_updater.js create mode 100755 test/manual/autoupdate/autoupdate.sh create mode 100644 test/manual/autoupdate/index.html create mode 100644 test/manual/autoupdate/nwjs.dummy.app/Contents/Info.plist create mode 100755 test/manual/autoupdate/nwjs.dummy.app/Contents/MacOS/nwjs create mode 100644 test/manual/autoupdate/nwjs.dummy.app/Contents/PkgInfo create mode 100644 test/manual/autoupdate/nwjs.dummy.app/Contents/Resources/Base.lproj/MainMenu.nib create mode 100644 test/manual/autoupdate/nwjs.dummy.app/Contents/_CodeSignature/CodeResources create mode 100644 test/manual/autoupdate/package.json diff --git a/.gitignore b/.gitignore index 57420b246c..3aac82dd2d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ tests/tmp-nw tags Thumbs.db /tmp +external_binaries diff --git a/BUILD.gn b/BUILD.gn index 585666aed8..71d78b3c79 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -3,6 +3,10 @@ import("//build/toolchain/toolchain.gni") if (is_mac) { import("//build/util/branding.gni") chrome_framework_name = chrome_product_full_name + " Framework" + cflags = [ + "-F", + "../../content/nw/external_binaries", + ] } import("//build/util/version.gni") @@ -12,7 +16,30 @@ group("nwjs") { "//third_party/ffmpeg:ffmpeg" ] if (is_mac) { - deps += [ ":copy_ffmpeg" ] + deps += [ ":copy_ffmpeg", ":copy_external_binaries" ] + } +} + +static_library("nw_autoupdater") { + deps = [ + "//base", + ] + sources = [ + "src/browser/auto_updater.h", + "src/browser/auto_updater.cc", + "src/browser/auto_updater_mac.mm", + ] + if (is_mac) { + libs = [ + "Squirrel.framework", + "ReactiveCocoa.framework", + "Mantle.framework", + ] + cflags_objcc = [ + "-Wno-error=unused-command-line-argument", + "-fobjc-runtime=macosx-10.8", + "-fobjc-weak", + ] } } @@ -92,6 +119,8 @@ nw_chrome_browser_sources = [ "src/api/object_manager.h", "src/api/object_manager_factory.cc", "src/api/object_manager_factory.h", + "src/api/auto_updater/api_auto_updater.cc", + "src/api/auto_updater/api_auto_updater.h", "src/api/base/base.cc", "src/api/base/base.h", "src/api/menu/menu.cc", @@ -121,6 +150,7 @@ static_library("nw_browser") { "//third_party/zlib:minizip", "//skia", ":nw_base", + ":nw_autoupdater", "//content/nw/src/api:nw_api", "//content/nw/src/api:nw_api_registration", "//third_party/protobuf:protobuf_lite", @@ -256,6 +286,14 @@ if (is_mac) { "//third_party/ffmpeg:ffmpeg" ] } + copy("copy_external_binaries") { + sources = [ + "external_binaries/Squirrel.framework", + "external_binaries/ReactiveCocoa.framework", + "external_binaries/Mantle.framework", + ] + outputs = [ "$root_out_dir/$chrome_product_full_name.app/Contents/Versions/$chrome_version_full/{{source_file_part}}" ] + } } copy("copy_node") { diff --git a/nw.gypi b/nw.gypi index 35776afa6d..a794030d36 100644 --- a/nw.gypi +++ b/nw.gypi @@ -37,8 +37,46 @@ }, }, }, + 'conditions': [ + ['OS=="mac"', { + 'mac_framework_dirs': [ + '<(DEPTH)/../content/nw/external_binaries', + ], + }], + ], + }, 'targets': [ + { + 'target_name': 'nw_autoupdater', + 'type': 'static_library', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + ], + 'sources': [ + 'src/browser/auto_updater.h', + 'src/browser/auto_updater.cc', + 'src/browser/auto_updater_mac.mm', + ], + 'conditions': [ + ['OS == "mac"', { + 'link_settings': { + 'libraries': [ + 'external_binaries/Squirrel.framework', + 'external_binaries/ReactiveCocoa.framework', + 'external_binaries/Mantle.framework', + ], + }, + 'xcode_settings': { + 'OTHER_CFLAGS': [ + '-Wno-error=unused-command-line-argument', + '-fobjc-runtime=macosx-10.8', + '-fobjc-weak', + ], + }, + }], + ], + }, { 'target_name': 'nw_base', 'type': '<(component)', @@ -71,6 +109,7 @@ '<(DEPTH)/third_party/zlib/zlib.gyp:minizip', '<(DEPTH)/skia/skia.gyp:skia', 'nw_base', + 'nw_autoupdater', '<(DEPTH)/content/nw/src/api/api.gyp:nw_api', '<(DEPTH)/content/nw/src/api/api_registration.gyp:nw_api_registration', '<(DEPTH)/extensions/browser/api/api_registration.gyp:extensions_api_registration', @@ -107,6 +146,8 @@ 'src/api/object_manager.h', 'src/api/object_manager_factory.cc', 'src/api/object_manager_factory.h', + 'src/api/auto_updater/api_auto_updater.cc', + 'src/api/auto_updater/api_auto_updater.h', 'src/api/base/base.cc', 'src/api/base/base.h', 'src/api/menu/menu.cc', @@ -574,5 +615,10 @@ }, } ], + 'xcode_settings': { + 'LD_RUNPATH_SEARCH_PATHS': [ + '@loader_path/..', + ], + }, } diff --git a/src/api/_api_features.json b/src/api/_api_features.json index c68d9107d9..e78d755957 100644 --- a/src/api/_api_features.json +++ b/src/api/_api_features.json @@ -11,6 +11,10 @@ "channel": "stable", "contexts": ["blessed_extension"] }, + "nw.AutoUpdater": { + "channel": "stable", + "contexts": ["blessed_extension"] + }, "nw.Window": { "channel": "stable", "contexts": ["blessed_extension"] diff --git a/src/api/auto_updater/api_auto_updater.cc b/src/api/auto_updater/api_auto_updater.cc new file mode 100644 index 0000000000..b3e53d00d8 --- /dev/null +++ b/src/api/auto_updater/api_auto_updater.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2016 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "base/values.h" +#include "content/nw/src/api/auto_updater/api_auto_updater.h" +#include "content/nw/src/api/nw_auto_updater.h" +#include "content/nw/src/browser/auto_updater.h" +#include "extensions/browser/extensions_browser_client.h" + +using namespace extensions::nwapi::nw__auto_updater; +using namespace content; + +namespace extensions { + + static void DispatchEvent(events::HistogramValue histogram_value, + const std::string& event_name, + std::unique_ptr args) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + ExtensionsBrowserClient::Get()->BroadcastEventToRenderers( + histogram_value, event_name, std::move(args)); + } + + + class AutoUpdaterObserver : public auto_updater::Delegate { + + // An error happened. + void OnError(const std::string& error) override { + std::unique_ptr arguments (new base::ListValue()); + arguments->AppendString(error); + DispatchEvent(events::HistogramValue::UNKNOWN, + nwapi::nw__auto_updater::OnError::kEventName, + std::move(arguments)); + } + + // Checking to see if there is an update + void OnCheckingForUpdate() override { + std::unique_ptr arguments (new base::ListValue()); + DispatchEvent(events::HistogramValue::UNKNOWN, + nwapi::nw__auto_updater::OnCheckingForUpdate::kEventName, + std::move(arguments)); + } + + // There is an update available and it is being downloaded + void OnUpdateAvailable() override { + std::unique_ptr arguments (new base::ListValue()); + DispatchEvent(events::HistogramValue::UNKNOWN, + nwapi::nw__auto_updater::OnUpdateAvailable::kEventName, + std::move(arguments)); + } + + // There is no available update. + void OnUpdateNotAvailable() override { + std::unique_ptr arguments (new base::ListValue()); + DispatchEvent(events::HistogramValue::UNKNOWN, + nwapi::nw__auto_updater::OnUpdateNotAvailable::kEventName, + std::move(arguments)); + } + + // There is a new update which has been downloaded. + void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) override { + std::unique_ptr arguments (new base::ListValue()); + arguments->AppendString(release_notes); + arguments->AppendString(release_name); + arguments->AppendDouble(release_date.ToJsTime()); + arguments->AppendString(update_url); + DispatchEvent(events::HistogramValue::UNKNOWN, + nwapi::nw__auto_updater::OnUpdateDownloaded::kEventName, + std::move(arguments)); + } + + public: + AutoUpdaterObserver() { + } + + ~AutoUpdaterObserver() override { + } + + }; + + AutoUpdaterObserver gAutoUpdateObserver; + NwAutoUpdaterNativeCallSyncFunction::NwAutoUpdaterNativeCallSyncFunction() {} + + bool NwAutoUpdaterNativeCallSyncFunction::RunNWSync(base::ListValue* response, std::string* error) { + if (!auto_updater::AutoUpdater::GetDelegate()) { + auto_updater::AutoUpdater::SetDelegate(&gAutoUpdateObserver); + } + + std::string method; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &method)); + + if (method == "SetFeedURL") { + auto_updater::AutoUpdater::HeaderMap headers; + std::string url; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &url)); + auto_updater::AutoUpdater::SetFeedURL(url, headers); + } else if (method == "GetFeedURL") { + std::string url = auto_updater::AutoUpdater::GetFeedURL(); + response->AppendString(url); + } else if (method == "QuitAndInstall") { + auto_updater::AutoUpdater::QuitAndInstall(); + } else if (method == "CheckForUpdates") { + auto_updater::AutoUpdater::CheckForUpdates(); + } + return true; + } + + +} // namespace extension diff --git a/src/api/auto_updater/api_auto_updater.h b/src/api/auto_updater/api_auto_updater.h new file mode 100644 index 0000000000..295eb2fb38 --- /dev/null +++ b/src/api/auto_updater/api_auto_updater.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +#ifndef CONTENT_NW_SRC_API_AUTO_UPDATER_H_ +#define CONTENT_NW_SRC_API_AUTO_UPDATER_H_ + +#include "extensions/browser/extension_function.h" + +namespace extensions { + + class NwAutoUpdaterNativeCallSyncFunction: public NWSyncExtensionFunction { + public: + NwAutoUpdaterNativeCallSyncFunction(); + bool RunNWSync(base::ListValue* response, std::string* error) override; + + protected: + ~NwAutoUpdaterNativeCallSyncFunction() override {} + DECLARE_EXTENSION_FUNCTION("nw.AutoUpdater.NativeCallSync", UNKNOWN) + + private: + DISALLOW_COPY_AND_ASSIGN(NwAutoUpdaterNativeCallSyncFunction); + }; + +} // namespace extensions + +#endif //CONTENT_NW_SRC_API_AUTO_UPDATER_H_ diff --git a/src/api/dispatcher_bindings.cc b/src/api/dispatcher_bindings.cc index 44f7c58b3b..a965733ed4 100644 --- a/src/api/dispatcher_bindings.cc +++ b/src/api/dispatcher_bindings.cc @@ -218,6 +218,8 @@ DispatcherBindings::RequireNwGui(const v8::FunctionCallbackInfo& args NwGui, global, v8::String::NewFromUtf8(isolate, "shortcut.js"), IDR_NW_API_SHORTCUT_JS); RequireFromResource(args.This(), NwGui, global, v8::String::NewFromUtf8(isolate, "screen.js"), IDR_NW_API_SCREEN_JS); + RequireFromResource(args.This(), + NwGui, global, v8::String::NewFromUtf8(isolate, "auto_updater.js"), IDR_NW_API_AUTO_UPDATE_JS); g_context->Exit(); args.GetReturnValue().Set(handle_scope.Escape(NwGui)); diff --git a/src/api/dispatcher_host.cc b/src/api/dispatcher_host.cc index f5f25e7936..d827a8e012 100644 --- a/src/api/dispatcher_host.cc +++ b/src/api/dispatcher_host.cc @@ -28,6 +28,7 @@ #include "content/browser/web_contents/web_contents_impl.h" #include "content/nw/src/api/api_messages.h" #include "content/nw/src/api/app/app.h" +#include "content/nw/src/api/auto_updater/api_auto_updater.h" #include "content/nw/src/api/base/base.h" #include "content/nw/src/api/clipboard/clipboard.h" #include "content/nw/src/api/event/event.h" @@ -171,6 +172,8 @@ void DispatcherHost::OnAllocateObject(int object_id, objects_registry_.AddWithID(new Shortcut(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else if (type == "Screen") { objects_registry_.AddWithID(new EventListener(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); + } else if (type == "AutoUpdater") { + objects_registry_.AddWithID(new EventListener(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); } else { LOG(ERROR) << "Allocate an object of unknown type: " << type; objects_registry_.AddWithID(new Base(object_id, weak_ptr_factory_.GetWeakPtr(), option), object_id); @@ -264,6 +267,9 @@ void DispatcherHost::OnCallStaticMethodSync( } else if (type == "Screen") { nwapi::Screen::Call(this, method, arguments, result); return; + } else if (type == "AutoUpdater") { + nwapi::AutoUpdater::Call(this, method, arguments, result); + return; } NOTREACHED() << "Calling unknown method " << method << " of class " << type; diff --git a/src/api/nw_auto_updater.idl b/src/api/nw_auto_updater.idl new file mode 100644 index 0000000000..ef57c25247 --- /dev/null +++ b/src/api/nw_auto_updater.idl @@ -0,0 +1,19 @@ +// Copyright 2016 The NW.js Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +// nw AutoUpdater API +[implemented_in="content/nw/src/api/auto_updater/api_auto_updater.h"] +namespace nw.AutoUpdater { + interface Events { + static void onError(DOMString msg); + static void onCheckingForUpdate(); + static void onUpdateAvailable(); + static void onUpdateNotAvailable(); + static void onUpdateDownloaded(DOMString releaseNotes, DOMString releaseName, double releaseDate, DOMString updateURL); + }; + + interface Functions { + static DOMString[] NativeCallSync(DOMString method, DOMString param); + }; +}; diff --git a/src/api/schemas.gni b/src/api/schemas.gni index 3e0d9ca639..0dd17568d3 100644 --- a/src/api/schemas.gni +++ b/src/api/schemas.gni @@ -12,6 +12,7 @@ assert(enable_extensions) schema_sources = [ "nw_object.idl", "nw_app.idl", + "nw_auto_updater.idl", "nw_window.idl", "nw_clipboard.idl", "nw_menu.idl", diff --git a/src/api/schemas.gypi b/src/api/schemas.gypi index 69a3c9af5e..34e5b40998 100644 --- a/src/api/schemas.gypi +++ b/src/api/schemas.gypi @@ -10,6 +10,7 @@ 'schema_files': [ 'nw_object.idl', 'nw_app.idl', + 'nw_auto_updater.idl', 'nw_window.idl', 'nw_clipboard.idl', 'nw_menu.idl', diff --git a/src/browser/auto_updater.cc b/src/browser/auto_updater.cc new file mode 100644 index 0000000000..e8fe995eac --- /dev/null +++ b/src/browser/auto_updater.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "content/nw/src/browser/auto_updater.h" + +namespace auto_updater { + +Delegate* AutoUpdater::delegate_ = nullptr; + +Delegate* AutoUpdater::GetDelegate() { + return delegate_; +} + +void AutoUpdater::SetDelegate(Delegate* delegate) { + delegate_ = delegate; +} + +#if !defined(OS_MACOSX) || defined(MAS_BUILD) +std::string AutoUpdater::GetFeedURL() { + return ""; +} + +void AutoUpdater::SetFeedURL(const std::string& url, + const HeaderMap& requestHeaders) { +} + +void AutoUpdater::CheckForUpdates() { +} + +void AutoUpdater::QuitAndInstall() { +} +#endif + +} // namespace auto_updater diff --git a/src/browser/auto_updater.h b/src/browser/auto_updater.h new file mode 100644 index 0000000000..da846f5d28 --- /dev/null +++ b/src/browser/auto_updater.h @@ -0,0 +1,66 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef NW_BROWSER_AUTO_UPDATER_H_ +#define NW_BROWSER_AUTO_UPDATER_H_ + +#include +#include + +#include "base/macros.h" +#include "build/build_config.h" + +namespace base { +class Time; +} + +namespace auto_updater { + +class Delegate { + public: + // An error happened. + virtual void OnError(const std::string& error) {} + + // Checking to see if there is an update + virtual void OnCheckingForUpdate() {} + + // There is an update available and it is being downloaded + virtual void OnUpdateAvailable() {} + + // There is no available update. + virtual void OnUpdateNotAvailable() {} + + // There is a new update which has been downloaded. + virtual void OnUpdateDownloaded(const std::string& release_notes, + const std::string& release_name, + const base::Time& release_date, + const std::string& update_url) {} + + protected: + virtual ~Delegate() {} +}; + +class AutoUpdater { + public: + typedef std::map HeaderMap; + + // Gets/Sets the delegate. + static Delegate* GetDelegate(); + static void SetDelegate(Delegate* delegate); + + static std::string GetFeedURL(); + static void SetFeedURL(const std::string& url, + const HeaderMap& requestHeaders); + static void CheckForUpdates(); + static void QuitAndInstall(); + + private: + static Delegate* delegate_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoUpdater); +}; + +} // namespace auto_updater + +#endif // NW_BROWSER_AUTO_UPDATER_H_ diff --git a/src/browser/auto_updater_mac.mm b/src/browser/auto_updater_mac.mm new file mode 100644 index 0000000000..d3f7eccaf9 --- /dev/null +++ b/src/browser/auto_updater_mac.mm @@ -0,0 +1,136 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "content/nw/src/browser/auto_updater.h" + +#import +#import +#import +#import + +#include "base/bind.h" +#include "base/time/time.h" +#include "base/strings/sys_string_conversions.h" + +namespace auto_updater { + +namespace { + +// The gloal SQRLUpdater object. +SQRLUpdater* g_updater = nil; + +} // namespace + +namespace { + +bool g_update_available = false; +std::string update_url_ = ""; + +} + +std::string AutoUpdater::GetFeedURL() { + return update_url_; +} + +// static +void AutoUpdater::SetFeedURL(const std::string& feed, + const HeaderMap& requestHeaders) { + Delegate* delegate = GetDelegate(); + if (!delegate) + return; + + update_url_ = feed; + + NSURL* url = [NSURL URLWithString:base::SysUTF8ToNSString(feed)]; + NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url]; + + for (const auto& it : requestHeaders) { + [urlRequest setValue:base::SysUTF8ToNSString(it.second) + forHTTPHeaderField:base::SysUTF8ToNSString(it.first)]; + } + + if (g_updater) + [g_updater release]; + + // Initialize the SQRLUpdater. + @try { + g_updater = [[SQRLUpdater alloc] initWithUpdateRequest:urlRequest]; + } @catch (NSException* error) { + delegate->OnError(base::SysNSStringToUTF8(error.reason)); + return; + } + + [[g_updater rac_valuesForKeyPath:@"state" observer:g_updater] + subscribeNext:^(NSNumber *stateNumber) { + int state = [stateNumber integerValue]; + // Dispatching the event on main thread. + dispatch_async(dispatch_get_main_queue(), ^{ + if (state == SQRLUpdaterStateCheckingForUpdate) + delegate->OnCheckingForUpdate(); + else if (state == SQRLUpdaterStateDownloadingUpdate) + delegate->OnUpdateAvailable(); + }); + }]; +} + +// static +void AutoUpdater::CheckForUpdates() { + Delegate* delegate = GetDelegate(); + if (!delegate) + return; + + [[[[g_updater.checkForUpdatesCommand + execute:nil] + // Send a `nil` after everything... + concat:[RACSignal return:nil]] + // But only take the first value. If an update is sent, we'll get that. + // Otherwise, we'll get our inserted `nil` value. + take:1] + subscribeNext:^(SQRLDownloadedUpdate *downloadedUpdate) { + if (downloadedUpdate) { + g_update_available = true; + SQRLUpdate* update = downloadedUpdate.update; + // There is a new update that has been downloaded. + delegate->OnUpdateDownloaded( + base::SysNSStringToUTF8(update.releaseNotes), + base::SysNSStringToUTF8(update.releaseName), + base::Time::FromDoubleT(update.releaseDate.timeIntervalSince1970), + base::SysNSStringToUTF8(update.updateURL.absoluteString)); + } else { + g_update_available = false; + // When the completed event is sent with no update, then we know there + // is no update available. + delegate->OnUpdateNotAvailable(); + } + } error:^(NSError *error) { + NSMutableString* failureString = + [NSMutableString stringWithString:error.localizedDescription]; + if (error.localizedFailureReason) { + [failureString appendString:@": "]; + [failureString appendString:error.localizedFailureReason]; + } + if (error.localizedRecoverySuggestion) { + if (![failureString hasSuffix:@"."]) + [failureString appendString:@"."]; + [failureString appendString:@" "]; + [failureString appendString:error.localizedRecoverySuggestion]; + } + delegate->OnError(base::SysNSStringToUTF8(failureString)); + }]; +} + +void AutoUpdater::QuitAndInstall() { + Delegate* delegate = AutoUpdater::GetDelegate(); + if (g_update_available) { + [[g_updater relaunchToInstallUpdate] subscribeError:^(NSError* error) { + if (delegate) + delegate->OnError(base::SysNSStringToUTF8(error.localizedDescription)); + }]; + } else { + if (delegate) + delegate->OnError("No update available, can't quit and install"); + } +} + +} // namespace auto_updater diff --git a/src/resources/api_nw_auto_updater.js b/src/resources/api_nw_auto_updater.js new file mode 100644 index 0000000000..8d7e8dd8ed --- /dev/null +++ b/src/resources/api_nw_auto_updater.js @@ -0,0 +1,258 @@ +// Copyright (c) 2016 Jefry Tedjokusumo +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co +// pies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in al +// l copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM +// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH +// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var AutoUpdater = null; +var EventEmitter = nw.require('events').EventEmitter; + +if (process.platform === 'win32') { + + const fs = nw.require('fs') + const path = nw.require('path') + const spawn = nw.require('child_process').spawn + + // i.e. my-app/app-0.1.13/ + const appFolder = path.dirname(process.execPath) + + // i.e. my-app/Update.exe + const updateExe = path.resolve(appFolder, '..', 'Update.exe') + const exeName = path.basename(process.execPath) + var spawnedArgs = [] + var spawnedProcess + + var isSameArgs = function (args) { + return (args.length === spawnedArgs.length) && args.every(function (e, i) { + return e === spawnedArgs[i] + }) + } + + // Spawn a command and invoke the callback when it completes with an error + // and the output from standard out. + var spawnUpdate = function (args, detached, callback) { + var error, errorEmitted, stderr, stdout + + try { + // Ensure we don't spawn multiple squirrel processes + // Process spawned, same args: Attach events to alread running process + // Process spawned, different args: Return with error + // No process spawned: Spawn new process + if (spawnedProcess && !isSameArgs(args)) { + return callback('AutoUpdater process with arguments ' + args + ' is already running') + } else if (!spawnedProcess) { + spawnedProcess = spawn(updateExe, args, { + detached: detached + }) + spawnedArgs = args || [] + } + } catch (error1) { + error = error1 + + // Shouldn't happen, but still guard it. + process.nextTick(function () { + return callback(error) + }) + return + } + stdout = '' + stderr = '' + spawnedProcess.stdout.on('data', function (data) { + stdout += data + }) + spawnedProcess.stderr.on('data', function (data) { + stderr += data + }) + errorEmitted = false + spawnedProcess.on('error', function (error) { + errorEmitted = true + callback(error) + }) + return spawnedProcess.on('exit', function (code, signal) { + spawnedProcess = undefined + spawnedArgs = [] + + // We may have already emitted an error. + if (errorEmitted) { + return + } + + // Process terminated with error. + if (code !== 0) { + return callback('Command failed: ' + (signal != null ? signal : code) + '\n' + stderr) + } + + // Success. + callback(null, stdout) + }) + } + + function Squirrel() { + } + // Start an instance of the installed app. + Squirrel.prototype.processStart = function () { + return spawnUpdate(['--processStartAndWait', exeName], true, function () {}) + } + + // Download the releases specified by the URL and write new results to stdout. + Squirrel.prototype.download = function (updateURL, callback) { + return spawnUpdate(['--download', updateURL], false, function (error, stdout) { + var json, ref, ref1, update + if (error != null) { + return callback(error) + } + try { + // Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop() + update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0 + } catch (jsonError) { + return callback('Invalid result:\n' + stdout) + } + return callback(null, update) + }) + } + + // Update the application to the latest remote version specified by URL. + Squirrel.prototype.update = function (updateURL, callback) { + return spawnUpdate(['--update', updateURL], false, callback) + } + + // Is the Update.exe installed with the current application? + Squirrel.prototype.supported = function () { + try { + fs.accessSync(updateExe, fs.R_OK) + return true + } catch (error) { + return false + } + } + const squirrelUpdate = new Squirrel(); + + // === AutoUpdaterWin === + + var AutoUpdaterWin = new EventEmitter(); + + AutoUpdaterWin.GetFeedURL = function() { + return this.updateURL; + } + + AutoUpdaterWin.SetFeedURL = function(url) { + this.updateURL = url; + } + + AutoUpdaterWin.emitError = function(message) { + this.emit('error', message); + } + + AutoUpdaterWin.QuitAndInstall = function() { + if (!this.updateAvailable) { + return this.emitError('No update available, can\'t quit and install'); + } + squirrelUpdate.processStart(); + nw.App.quit(); + } + + AutoUpdaterWin.CheckForUpdates = function() { + if (!this.updateURL) { + return this.emitError('Update URL is not set') + } + if (!squirrelUpdate.supported()) { + return this.emitError('Can not find Squirrel') + } + this.emit('checking-for-update') + squirrelUpdate.download(this.updateURL, function(error, update) { + if (error != null) { + return AutoUpdaterWin.emitError(error) + } + if (update == null) { + return AutoUpdaterWin.emit('update-not-available') + } + AutoUpdaterWin.updateAvailable = true + AutoUpdaterWin.emit('update-available') + squirrelUpdate.update(AutoUpdaterWin.updateURL, function(error) { + if (error != null) { + return AutoUpdaterWin.emitError(error) + } + const releaseNotes = update + // Date is not available on Windows, so fake it. + const date = new Date() + AutoUpdaterWin.emit('update-downloaded', releaseNotes, date, AutoUpdaterWin.updateURL) + }) + }) + } + + AutoUpdater = AutoUpdaterWin; + +} else { + var nw_binding = require('binding').Binding.create('nw.AutoUpdater'); + var sendRequest = require('sendRequest'); + + var events = { + onError: 'error', + onCheckingForUpdate: 'checking-for-update', + onUpdateAvailable: 'update-available', + onUpdateNotAvailable: 'update-not-available', + onUpdateDownloaded: 'update-downloaded' + }; + + // Hook Sync API calls + nw_binding.registerCustomHook(function(bindingsAPI) { + var apiFunctions = bindingsAPI.apiFunctions; + ['NativeCallSync'].forEach(function(nwSyncAPIName) { + apiFunctions.setHandleRequest(nwSyncAPIName, function() { + return sendRequest.sendRequestSync(this.name, arguments, this.definition.parameters, {}); + }); + }); + }); + + var nwAutoUpdaterBinding = nw_binding.generate(); + + var AutoUpdaterNative = new EventEmitter(); + + AutoUpdaterNative.SetFeedURL = function (url) { + nwAutoUpdaterBinding.NativeCallSync("SetFeedURL", url); + } + + AutoUpdaterNative.GetFeedURL = function () { + return nwAutoUpdaterBinding.NativeCallSync("GetFeedURL", "")[0]; + } + + AutoUpdaterNative.CheckForUpdates = function () { + nwAutoUpdaterBinding.NativeCallSync("CheckForUpdates", ""); + } + + AutoUpdaterNative.QuitAndInstall = function () { + nwAutoUpdaterBinding.NativeCallSync("QuitAndInstall", ""); + } + + Object.keys(events).forEach(function(eventName) { + nwAutoUpdaterBinding[eventName].addListener(function() { + var args = Array.prototype.concat.apply([events[eventName]], arguments); + if(eventName == 'onUpdateDownloaded') { + args[3] = Date(args[3]); + } + AutoUpdaterNative.emit.apply(AutoUpdaterNative, args); + }); + }); + + AutoUpdater = AutoUpdaterNative; +} + +AutoUpdater.Init = function() { + //do nothing, compatibility with old api +} + +exports.binding = AutoUpdater; diff --git a/src/resources/nw_resources.grd b/src/resources/nw_resources.grd index 30f4dff82d..0179766310 100644 --- a/src/resources/nw_resources.grd +++ b/src/resources/nw_resources.grd @@ -50,6 +50,7 @@ + diff --git a/test/manual/autoupdate/autoupdate.sh b/test/manual/autoupdate/autoupdate.sh new file mode 100755 index 0000000000..a1f14c3388 --- /dev/null +++ b/test/manual/autoupdate/autoupdate.sh @@ -0,0 +1,33 @@ + +if [ "$#" -ne 2 ] || ! [ -d "$2" ]; then + echo "Usage: $0 IDENTITY DIRECTORY" >&2 + exit 1 +fi + +identity="$1" +rm -r nwjs.app +cp -r $2/nwjs.app . + +codesign --force --verify --verbose --sign "$identity" nwjs.dummy.app +zip -r nwjs.dummy.zip nwjs.dummy.app/ + +app="nwjs.app" +version=$(ls "$app"/Contents/Versions) + +echo "### signing frameworks" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/nwjs Framework.framework/Helpers/crashpad_handler" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/nwjs Framework.framework/libffmpeg.dylib" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/nwjs Framework.framework/libnode.dylib" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/nwjs Framework.framework/" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/nwjs Helper.app/" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/Mantle.framework/" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/ReactiveCocoa.framework/" +codesign --force --verify --verbose --sign "$identity" "$app/Contents/Versions/$version/Squirrel.framework/" + +echo "### signing app" +codesign --force --verify --verbose --sign "$identity" "$app" + +echo "### verifying signature" +codesign -vvv -d "$app" + +nwjs.app/Contents/MacOS/nwjs diff --git a/test/manual/autoupdate/index.html b/test/manual/autoupdate/index.html new file mode 100644 index 0000000000..482f1b49eb --- /dev/null +++ b/test/manual/autoupdate/index.html @@ -0,0 +1,57 @@ + + + + + + nwjs autoupdate + + + + +

+ + diff --git a/test/manual/autoupdate/nwjs.dummy.app/Contents/Info.plist b/test/manual/autoupdate/nwjs.dummy.app/Contents/Info.plist new file mode 100644 index 0000000000..fcfdc35fd8 --- /dev/null +++ b/test/manual/autoupdate/nwjs.dummy.app/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 15G1108 + CFBundleDevelopmentRegion + en + CFBundleExecutable + nwjs + CFBundleIdentifier + io.nwjs.nwjs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + nwjs + CFBundlePackageType + APPL + CFBundleShortVersionString + 999.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 8C1002 + DTPlatformVersion + GM + DTSDKBuild + 16C58 + DTSDKName + macosx10.12 + DTXcode + 0821 + DTXcodeBuild + 8C1002 + LSMinimumSystemVersion + 10.11 + NSHumanReadableCopyright + Copyright © 2017 io.nwjs. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/test/manual/autoupdate/nwjs.dummy.app/Contents/MacOS/nwjs b/test/manual/autoupdate/nwjs.dummy.app/Contents/MacOS/nwjs new file mode 100755 index 0000000000000000000000000000000000000000..487b572e5e3f79b45c88a818ffed4f2ce2c4742e GIT binary patch literal 24608 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 literal 0 HcmV?d00001 diff --git a/test/manual/autoupdate/nwjs.dummy.app/Contents/PkgInfo b/test/manual/autoupdate/nwjs.dummy.app/Contents/PkgInfo new file mode 100644 index 0000000000..bd04210fb4 --- /dev/null +++ b/test/manual/autoupdate/nwjs.dummy.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/test/manual/autoupdate/nwjs.dummy.app/Contents/Resources/Base.lproj/MainMenu.nib b/test/manual/autoupdate/nwjs.dummy.app/Contents/Resources/Base.lproj/MainMenu.nib new file mode 100644 index 0000000000000000000000000000000000000000..a7d0173ca33d8424f79cc3c106aaaa23f148a68e GIT binary patch literal 36915 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 literal 0 HcmV?d00001 diff --git a/test/manual/autoupdate/nwjs.dummy.app/Contents/_CodeSignature/CodeResources b/test/manual/autoupdate/nwjs.dummy.app/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000000..8d8b6db480 --- /dev/null +++ b/test/manual/autoupdate/nwjs.dummy.app/Contents/_CodeSignature/CodeResources @@ -0,0 +1,129 @@ + + + + + files + + Resources/Base.lproj/MainMenu.nib + + hash + + LQ4qi4aOzondJ2SCCiWE8hK8IzQ= + + optional + + + + files2 + + Resources/Base.lproj/MainMenu.nib + + hash + + LQ4qi4aOzondJ2SCCiWE8hK8IzQ= + + hash2 + + R8+UXVFhR2c8A7NnqADgJJ6LeJ4mruHxEYVRqN88Dco= + + optional + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/test/manual/autoupdate/package.json b/test/manual/autoupdate/package.json new file mode 100644 index 0000000000..43684b8ad9 --- /dev/null +++ b/test/manual/autoupdate/package.json @@ -0,0 +1,4 @@ +{ + "name": "nwjs.autoupdate", + "main": "index.html" +} diff --git a/tools/package_binaries.py b/tools/package_binaries.py index 855980a8b6..4da840f038 100755 --- a/tools/package_binaries.py +++ b/tools/package_binaries.py @@ -9,6 +9,7 @@ import sys import tarfile import zipfile +import sys from hashlib import sha256 from subprocess import call @@ -353,15 +354,21 @@ def compress(from_dir, to_dir, fname, compress): _from = os.path.join(from_dir, fname) _to = os.path.join(to_dir, fname) if compress == 'zip': - z = zipfile.ZipFile(_to + '.zip', 'w', compression=zipfile.ZIP_DEFLATED) - if os.path.isdir(_from): - for root, dirs, files in os.walk(_from): - for f in files: - _path = os.path.join(root, f) - z.write(_path, _path.replace(from_dir+os.sep, '')) + if sys.platform.startswith('darwin'): + cwd = os.getcwd() + os.chdir(from_dir) + subprocess.check_output(['zip', '-r', '-y', _to+'.zip', fname], stderr=subprocess.STDOUT, env=os.environ) + os.chdir(cwd) else: - z.write(_from, fname) - z.close() + z = zipfile.ZipFile(_to + '.zip', 'w', compression=zipfile.ZIP_DEFLATED) + if os.path.isdir(_from): + for root, dirs, files in os.walk(_from): + for f in files: + _path = os.path.join(root, f) + z.write(_path, _path.replace(from_dir+os.sep, '')) + else: + z.write(_from, fname) + z.close() elif compress == 'tar.gz': # only for folders if not os.path.isdir(_from): print 'Will not create tar.gz for a single file: ' + _from @@ -424,13 +431,13 @@ def make_packages(targets): src = os.path.join(binaries_location, f) dest = os.path.join(folder, f) if os.path.isdir(src): # like nw.app - shutil.copytree(src, dest) + shutil.copytree(src, dest, True) else: shutil.copy(src, dest) compress(dist_dir, dist_dir, t['output'], t['compress']) # remove temp folders if (t.has_key('keep4test')) : - shutil.copytree(folder, nwfolder) + shutil.copytree(folder, nwfolder, True) shutil.rmtree(folder) else: