src/extensions/default/AutoUpdate/main.js
/*
* Copyright (c) 2018 - present Adobe Systems Incorporated. All rights reserved.
*
* 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 copies 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
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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.
*
*/
/* eslint-disable indent */
/* eslint-disable max-len */
define(function (require, exports, module) {
"use strict";
var CommandManager = brackets.getModule("command/CommandManager"),
ProjectManager = brackets.getModule("project/ProjectManager"),
Commands = brackets.getModule("command/Commands"),
AppInit = brackets.getModule("utils/AppInit"),
UpdateNotification = brackets.getModule("utils/UpdateNotification"),
DocumentCommandHandlers = brackets.getModule("document/DocumentCommandHandlers"),
NodeDomain = brackets.getModule("utils/NodeDomain"),
FileSystem = brackets.getModule("filesystem/FileSystem"),
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
FileUtils = brackets.getModule("file/FileUtils"),
Strings = brackets.getModule("strings"),
HealthLogger = brackets.getModule("utils/HealthLogger"),
StateHandlerModule = require("StateHandler"),
MessageIds = require("MessageIds"),
UpdateStatus = require("UpdateStatus"),
UpdateInfoBar = require("UpdateInfoBar");
var _modulePath = FileUtils.getNativeModuleDirectoryPath(module),
_nodePath = "node/AutoUpdateDomain",
_domainPath = [_modulePath, _nodePath].join("/"),
updateDomain,
domainID;
var appSupportDirectory = brackets.app.getApplicationSupportDirectory(),
updateDir = appSupportDirectory + '/updateTemp',
updateJsonPath = updateDir + '/' + 'updateHelper.json';
var StateHandler = StateHandlerModule.StateHandler,
StateHandlerMessages = StateHandlerModule.MessageKeys;
var updateJsonHandler;
var MAX_DOWNLOAD_ATTEMPTS = 6,
downloadAttemptsRemaining;
// Below Strings are to identify an AutoUpdate Event.
var autoUpdateEventNames = {
AUTOUPDATE_DOWNLOAD_START: "DownloadStarted",
AUTOUPDATE_DOWNLOAD_COMPLETED: "DownloadCompleted",
AUTOUPDATE_DOWNLOAD_FAILED: "DownloadFailed",
AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_RESTART: "DownloadCompletedAndUserClickedRestart",
AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_LATER: "DownloadCompletedAndUserClickedLater",
AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED: "DownloadCompleteUpdateBarRendered",
AUTOUPDATE_INSTALLATION_FAILED: "InstallationFailed",
AUTOUPDATE_INSTALLATION_SUCCESS: "InstallationSuccess",
AUTOUPDATE_CLEANUP_FAILED: "AutoUpdateCleanUpFailed"
};
// function map for brackets functions
var functionMap = {};
var _updateParams;
//Namespacing the event
var APP_QUIT_CANCELLED = DocumentCommandHandlers.APP_QUIT_CANCELLED + ".auto-update";
var _nodeErrorMessages = {
UPDATEDIR_READ_FAILED: 0,
UPDATEDIR_CLEAN_FAILED: 1,
CHECKSUM_DID_NOT_MATCH: 2,
INSTALLER_NOT_FOUND: 3,
DOWNLOAD_ERROR: 4,
NETWORK_SLOW_OR_DISCONNECTED: 5
};
var updateProgressKey = "autoUpdateInProgress",
isAutoUpdateInitiated = false;
/**
* Checks if an auto update session is currently in progress
* @returns {boolean} - true if an auto update session is currently in progress, false otherwise
*/
function checkIfAnotherSessionInProgress() {
var result = $.Deferred();
if(updateJsonHandler) {
var state = updateJsonHandler.state;
updateJsonHandler.refresh()
.done(function() {
var val = updateJsonHandler.get(updateProgressKey);
if(val !== null) {
result.resolve(val);
} else {
result.reject();
}
})
.fail(function() {
updateJsonHandler.state = state;
result.reject();
});
}
return result.promise();
}
/**
* Checks if auto update preference is enabled or disabled
* @private
* @returns {boolean} - true if preference enabled, false otherwise
*/
function _isAutoUpdateEnabled() {
return (PreferencesManager.get("autoUpdate.AutoUpdate") !== false);
}
/**
* Receives messages from node
* @param {object} event - event received from node
* @param {object} msgObj - json containing - {
* fn - function to execute on Brackets side
* args - arguments to the above function
* requester - ID of the current requester domain
*/
function receiveMessageFromNode(event, msgObj) {
if (functionMap[msgObj.fn]) {
if(domainID === msgObj.requester) {
functionMap[msgObj.fn].apply(null, msgObj.args);
}
}
}
/*
* Checks if Brackets version got updated
* @returns {boolean} true if version updated, false otherwise
*/
function checkIfVersionUpdated() {
var latestBuildNumber = updateJsonHandler.get("latestBuildNumber"),
currentBuildNumber = Number(/-([0-9]+)/.exec(brackets.metadata.version)[1]);
return latestBuildNumber === currentBuildNumber;
}
/**
* Gets the arguments to a function in an array
* @param {object} args - the arguments object
* @returns {Array} - array of actual arguments
*/
function getFunctionArgs(args) {
if (args.length > 1) {
var fnArgs = new Array(args.length - 1),
i;
for (i = 1; i < args.length; ++i) {
fnArgs[i - 1] = args[i];
}
return fnArgs;
}
return [];
}
/**
* Posts messages to node
* @param {string} messageId - Message to be passed
*/
function postMessageToNode(messageId) {
var msg = {
fn: messageId,
args: getFunctionArgs(arguments),
requester: domainID
};
updateDomain.exec('data', msg);
}
/**
* Checks Install failure scenarios
*/
function checkInstallationStatus() {
var searchParams = {
"updateDir": updateDir,
"installErrorStr": ["ERROR:"],
"bracketsErrorStr": ["ERROR:"],
"encoding": "utf8"
},
// Below are the possible Windows Installer error strings, which will be searched for in the installer logs to track failures.
winInstallErrorStrArr = [
"Installation success or error status",
"Reconfiguration success or error status"
];
if (brackets.platform === "win") {
searchParams.installErrorStr = winInstallErrorStrArr;
searchParams.encoding = "utf16le";
}
postMessageToNode(MessageIds.CHECK_INSTALLER_STATUS, searchParams);
}
/**
* Checks and handles the update success and failure scenarios
*/
function checkUpdateStatus() {
var filesToCache = ['.logs'],
downloadCompleted = updateJsonHandler.get("downloadCompleted"),
updateInitiatedInPrevSession = updateJsonHandler.get("updateInitiatedInPrevSession");
if (downloadCompleted && updateInitiatedInPrevSession) {
var isNewVersion = checkIfVersionUpdated();
updateJsonHandler.reset();
if (isNewVersion) {
// We get here if the update was successful
UpdateInfoBar.showUpdateBar({
type: "success",
title: Strings.UPDATE_SUCCESSFUL,
description: ""
});
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_INSTALLATION_SUCCESS,
"autoUpdate",
"install",
"complete",
""
);
} else {
// We get here if the update started but failed
checkInstallationStatus();
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.UPDATE_FAILED,
description: Strings.GO_TO_SITE
});
}
} else if (downloadCompleted && !updateInitiatedInPrevSession) {
// We get here if the download was complete and user selected UpdateLater
if (brackets.platform === "mac") {
filesToCache = ['.dmg', '.json'];
} else if (brackets.platform === "win") {
filesToCache = ['.msi', '.json'];
}
}
postMessageToNode(MessageIds.PERFORM_CLEANUP, filesToCache);
}
/**
* Send Installer Error Code to Analytics Server
*/
function handleInstallationStatus(statusObj) {
var errorCode = "",
errorline = statusObj.installError;
if (errorline) {
errorCode = errorline.substr(errorline.lastIndexOf(':') + 2, errorline.length);
}
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_INSTALLATION_FAILED,
"autoUpdate",
"install",
"fail",
errorCode
);
}
/**
* Initializes the state of parsed content from updateHelper.json
* returns Promise Object Which is resolved when parsing is success
* and rejected if parsing is failed.
*/
function initState() {
var result = $.Deferred();
updateJsonHandler.parse()
.done(function() {
result.resolve();
})
.fail(function (code) {
var logMsg;
switch (code) {
case StateHandlerMessages.FILE_NOT_FOUND:
logMsg = "AutoUpdate : updateHelper.json cannot be parsed, does not exist";
break;
case StateHandlerMessages.FILE_NOT_READ:
logMsg = "AutoUpdate : updateHelper.json could not be read";
break;
case StateHandlerMessages.FILE_PARSE_EXCEPTION:
logMsg = "AutoUpdate : updateHelper.json could not be parsed, exception encountered";
break;
case StateHandlerMessages.FILE_READ_FAIL:
logMsg = "AutoUpdate : updateHelper.json could not be parsed";
break;
}
console.log(logMsg);
result.reject();
});
return result.promise();
}
/**
* Sets up the Auto Update environment
*/
function setupAutoUpdate() {
updateJsonHandler = new StateHandler(updateJsonPath);
updateDomain.on('data', receiveMessageFromNode);
updateDomain.exec('initNode', {
messageIds: MessageIds,
updateDir: updateDir,
requester: domainID
});
}
/**
* Initializes the state for AutoUpdate process
* @returns {$.Deferred} - a jquery promise,
* that is resolved with success or failure
* of state initialization
*/
function initializeState() {
var result = $.Deferred();
FileSystem.resolve(updateDir, function (err) {
if (!err) {
result.resolve();
} else {
var directory = FileSystem.getDirectoryForPath(updateDir);
directory.create(function (error) {
if (error) {
console.error('AutoUpdate : Error in creating update directory in Appdata');
result.reject();
} else {
result.resolve();
}
});
}
});
return result.promise();
}
/**
* Handles the auto update event, which is triggered when user clicks GetItNow in UpdateNotification dialog
* @param {object} updateParams - json object containing update information {
* installerName - name of the installer
* downloadURL - download URL
* latestBuildNumber - build number
* checksum - checksum }
*/
function initiateAutoUpdate(updateParams) {
_updateParams = updateParams;
downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS;
initializeState()
.done(function () {
var setUpdateInProgress = function() {
var initNodeFn = function () {
isAutoUpdateInitiated = true;
postMessageToNode(MessageIds.INITIALIZE_STATE, _updateParams);
};
if (updateJsonHandler.get('latestBuildNumber') !== _updateParams.latestBuildNumber) {
setUpdateStateInJSON('latestBuildNumber', _updateParams.latestBuildNumber)
.done(initNodeFn);
} else {
initNodeFn();
}
};
checkIfAnotherSessionInProgress()
.done(function(inProgress) {
if(inProgress) {
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.AUTOUPDATE_ERROR,
description: Strings.AUTOUPDATE_IN_PROGRESS
});
} else {
setUpdateStateInJSON("autoUpdateInProgress", true)
.done(setUpdateInProgress);
}
})
.fail(function() {
setUpdateStateInJSON("autoUpdateInProgress", true)
.done(setUpdateInProgress);
});
})
.fail(function () {
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.INITIALISATION_FAILED,
description: ""
});
});
}
/**
* Typical signature of an update entry, with the most frequently used keys
* @typedef {Object} Update~Entry
* @property {Number} buildNumber - The build number for the update
* @property {string} versionString - Version for the update
* @property {string} releaseNotesURL - URL for release notes for the update
* @property {array} newFeatures - Array of new features in the update
* @property {boolean} prerelease - Boolean to distinguish prerelease from a stable release
* @property {Object} platforms - JSON object, containing asset info for the update, for each platform
* Asset info for each platform consists of :
* @property {string} checksum - checksum of the asset
* @property {string} downloadURL - download URL of the asset
*
*/
/**
* Handles and processes the update info, required for app auto update
* @private
* @param {Array} updates - array of {...Update~Entry} update entries
*/
function _updateProcessHandler(updates) {
if (!updates) {
console.warn("AutoUpdate : updates information not available.");
return;
}
var OS = brackets.getPlatformInfo(),
checksum,
downloadURL,
installerName,
platforms,
latestUpdate;
latestUpdate = updates[0];
platforms = latestUpdate ? latestUpdate.platforms : null;
if (platforms && platforms[OS]) {
//If no checksum field is present then we're setting it to 0, just as a safety check,
// although ideally this situation should never occur in releases post its introduction.
checksum = platforms[OS].checksum ? platforms[OS].checksum : 0,
downloadURL = platforms[OS].downloadURL ? platforms[OS].downloadURL : "",
installerName = downloadURL ? downloadURL.split("/").pop() : "";
} else {
// Update not present for current platform
return false;
}
if (!checksum || !downloadURL || !installerName) {
console.warn("AutoUpdate : asset information incorrect for the update");
return false;
}
var updateParams = {
downloadURL: downloadURL,
installerName: installerName,
latestBuildNumber: latestUpdate.buildNumber,
checksum: checksum
};
//Initiate the auto update, with update params
initiateAutoUpdate(updateParams);
//Send a truthy value to ensure caller is informed about successful initialization of auto-update
return true;
}
/**
* Unregisters the App Quit event handler
*/
function resetAppQuitHandler() {
DocumentCommandHandlers.off(APP_QUIT_CANCELLED);
}
/**
* Unsets the Auto Update environment
*/
function unsetAutoUpdate() {
updateJsonHandler = null;
updateDomain.off('data');
resetAppQuitHandler();
}
/**
* Defines preference to enable/disable Auto Update
*/
function setupAutoUpdatePreference() {
PreferencesManager.definePreference("autoUpdate.AutoUpdate", "boolean", true, {
description: Strings.DESCRIPTION_AUTO_UPDATE
});
// Set or unset the auto update, based on preference state change
PreferencesManager.on("change", "autoUpdate.AutoUpdate", function () {
if (_isAutoUpdateEnabled()) {
setupAutoUpdate();
UpdateNotification.registerUpdateHandler(_updateProcessHandler);
} else {
unsetAutoUpdate();
UpdateNotification.resetToDefaultUpdateHandler();
}
});
}
/**
* Creates the Node Domain for Auto Update
*/
function setupAutoUpdateDomain() {
updateDomain = new NodeDomain("AutoUpdate", _domainPath);
}
/**
* Overriding the appReady for Auto update
*/
AppInit.appReady(function () {
// Auto Update is supported on Win and Mac, as of now
if (brackets.platform === "linux" || !(brackets.app.setUpdateParams)) {
return;
}
setupAutoUpdateDomain();
//Bail out if update domain could not be created
if (!updateDomain) {
return;
}
// Check if the update domain is properly initialised
updateDomain.promise()
.done(function () {
setupAutoUpdatePreference();
if (_isAutoUpdateEnabled()) {
domainID = (new Date()).getTime().toString();
setupAutoUpdate();
UpdateNotification.registerUpdateHandler(_updateProcessHandler);
}
})
.fail(function (err) {
console.error("AutoUpdate : node domain could not be initialized.");
return;
});
});
/**
* Enables/disables the state of "Auto Update In Progress" in UpdateHandler.json
*/
function nodeDomainInitialized(reset) {
initState()
.done(function () {
var inProgress = updateJsonHandler.get(updateProgressKey);
if (inProgress && reset) {
setUpdateStateInJSON(updateProgressKey, !reset)
.always(checkUpdateStatus);
} else if (!inProgress) {
checkUpdateStatus();
}
});
}
/**
* Enables/disables the state of "Check For Updates" menu entry under Help Menu
*/
function enableCheckForUpdateEntry(enable) {
var cfuCommand = CommandManager.get(Commands.HELP_CHECK_FOR_UPDATE);
cfuCommand.setEnabled(enable);
UpdateNotification.enableUpdateNotificationIcon(enable);
}
/**
* Checks if it is the first iteration of download
* @returns {boolean} - true if first iteration, false if it is a retrial of download
*/
function isFirstIterationDownload() {
return (downloadAttemptsRemaining === MAX_DOWNLOAD_ATTEMPTS);
}
/**
* Resets the update state in updatehelper.json in case of failure,
* and logs an error with the message
* @param {string} message - the message to be logged onto console
*/
function resetStateInFailure(message) {
updateJsonHandler.reset();
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.UPDATE_FAILED,
description: ""
});
enableCheckForUpdateEntry(true);
console.error(message);
}
/**
* Sets the update state in updateHelper.json in Appdata
* @param {string} key - key to be set
* @param {string} value - value to be set
* @returns {$.Deferred} - a jquery promise, that is resolved with
* success or failure of state update in json file
*/
function setUpdateStateInJSON(key, value) {
var result = $.Deferred();
updateJsonHandler.set(key, value)
.done(function () {
result.resolve();
})
.fail(function () {
resetStateInFailure("AutoUpdate : Could not modify updatehelper.json");
result.reject();
});
return result.promise();
}
/**
* Handles a safe download of the latest installer,
* safety is ensured by cleaning up any pre-existing installers
* from update directory before beginning a fresh download
*/
function handleSafeToDownload() {
var downloadFn = function () {
if (isFirstIterationDownload()) {
// For the first iteration of download, show download
//status info in Status bar, and pass download to node
UpdateStatus.showUpdateStatus("initial-download");
postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, true);
} else {
/* For the retry iterations of download, modify the
download status info in Status bar, and pass download to node */
var attempt = (MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining);
if (attempt > 1) {
var info = attempt.toString() + "/5";
var status = {
target: "retry-download",
spans: [{
id: "attempt",
val: info
}]
};
UpdateStatus.modifyUpdateStatus(status);
} else {
UpdateStatus.showUpdateStatus("retry-download");
}
postMessageToNode(MessageIds.DOWNLOAD_INSTALLER, false);
}
--downloadAttemptsRemaining;
};
if(!isAutoUpdateInitiated) {
isAutoUpdateInitiated = true;
updateJsonHandler.refresh()
.done(function() {
setUpdateStateInJSON('downloadCompleted', false)
.done(downloadFn);
});
} else {
setUpdateStateInJSON('downloadCompleted', false)
.done(downloadFn);
}
}
/**
* Checks if there is an active internet connection available
* @returns {boolean} - true if online, false otherwise
*/
function checkIfOnline() {
return window.navigator.onLine;
}
/**
* Attempts a download of the latest installer, while cleaning up any existing downloaded installers
*/
function attemptToDownload() {
if (checkIfOnline()) {
postMessageToNode(MessageIds.PERFORM_CLEANUP, ['.json'], true);
} else {
enableCheckForUpdateEntry(true);
UpdateStatus.cleanUpdateStatus();
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED,
"autoUpdate",
"download",
"fail",
"No Internet connection available."
);
UpdateInfoBar.showUpdateBar({
type: "warning",
title: Strings.DOWNLOAD_FAILED,
description: Strings.INTERNET_UNAVAILABLE
});
setUpdateStateInJSON("autoUpdateInProgress", false);
}
}
/**
* Validates the checksum of a file against a given checksum
* @param {object} params - json containing {
* filePath - path to the file,
* expectedChecksum - the checksum to validate against }
*/
function validateChecksum(params) {
postMessageToNode(MessageIds.VALIDATE_INSTALLER, params);
}
/**
* Gets the latest installer, by either downloading a new one or fetching the cached download.
*/
function getLatestInstaller() {
var downloadCompleted = updateJsonHandler.get('downloadCompleted');
if (!downloadCompleted) {
attemptToDownload();
} else {
validateChecksum();
}
}
/**
* Handles the show status information callback from Node.
* It modifies the info displayed on Status bar.
* @param {object} statusObj - json containing status info {
* target - id of string to display,
* spans - Array containing json objects of type - {
* id - span id,
* val - string to fill the span element with }
* }
*/
function showStatusInfo(statusObj) {
if (statusObj.target === "initial-download") {
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_START,
"autoUpdate",
"download",
"started",
""
);
UpdateStatus.modifyUpdateStatus(statusObj);
}
UpdateStatus.displayProgress(statusObj);
}
/**
* Handles the error messages from Node, in a popup displayed to the user.
* @param {string} message - error string
*/
function showErrorMessage(message) {
var analyticsDescriptionMessage = "";
switch (message) {
case _nodeErrorMessages.UPDATEDIR_READ_FAILED:
analyticsDescriptionMessage = "Update directory could not be read.";
break;
case _nodeErrorMessages.UPDATEDIR_CLEAN_FAILED:
analyticsDescriptionMessage = "Update directory could not be cleaned.";
break;
}
console.log("AutoUpdate : Clean-up failed! Reason : " + analyticsDescriptionMessage + ".\n");
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_CLEANUP_FAILED,
"autoUpdate",
"cleanUp",
"fail",
analyticsDescriptionMessage
);
}
/**
* Handles the Cancel button click by user in
* Unsaved changes prompt, which would come up if user
* has dirty files and he/she clicked UpdateNow
*/
function dirtyFileSaveCancelled() {
UpdateInfoBar.showUpdateBar({
type: "warning",
title: Strings.WARNING_TYPE,
description: Strings.UPDATE_ON_NEXT_LAUNCH
});
}
/**
* Registers the App Quit event handler, in case of dirty
* file save cancelled scenario, while Auto Update is scheduled to run on quit
*/
function setAppQuitHandler() {
resetAppQuitHandler();
DocumentCommandHandlers.on(APP_QUIT_CANCELLED, dirtyFileSaveCancelled);
}
/**
* Initiates the update process, when user clicks UpdateNow in the update popup
* @param {string} formattedInstallerPath - formatted path to the latest installer
* @param {string} formattedLogFilePath - formatted path to the installer log file
* @param {string} installStatusFilePath - path to the install status log file
*/
function initiateUpdateProcess(formattedInstallerPath, formattedLogFilePath, installStatusFilePath) {
// Get additional update parameters on Mac : installDir, appName, and updateDir
function getAdditionalParams() {
var retval = {};
var installDir = FileUtils.getNativeBracketsDirectoryPath();
if (installDir) {
var appPath = installDir.split("/Contents/www")[0];
installDir = appPath.substr(0, appPath.lastIndexOf('/'));
var appName = appPath.substr(appPath.lastIndexOf('/') + 1);
retval = {
installDir: installDir,
appName: appName,
updateDir: updateDir
};
}
return retval;
}
// Update function, to carry out app update
var updateFn = function () {
var infoObj = {
installerPath: formattedInstallerPath,
logFilePath: formattedLogFilePath,
installStatusFilePath: installStatusFilePath
};
if (brackets.platform === "mac") {
var additionalParams = getAdditionalParams(),
key;
for (key in additionalParams) {
if (additionalParams.hasOwnProperty(key)) {
infoObj[key] = additionalParams[key];
}
}
}
// Set update parameters for app update
if (brackets.app.setUpdateParams) {
brackets.app.setUpdateParams(JSON.stringify(infoObj), function (err) {
if (err) {
resetStateInFailure("AutoUpdate : Update parameters could not be set for the installer. Error encountered: " + err);
} else {
setAppQuitHandler();
CommandManager.execute(Commands.FILE_QUIT);
}
});
} else {
resetStateInFailure("AutoUpdate : setUpdateParams could not be found in shell");
}
};
setUpdateStateInJSON('updateInitiatedInPrevSession', true)
.done(updateFn);
}
/**
* Detaches the Update Bar Buttons event handlers
*/
function detachUpdateBarBtnHandlers() {
UpdateInfoBar.off(UpdateInfoBar.RESTART_BTN_CLICKED);
UpdateInfoBar.off(UpdateInfoBar.LATER_BTN_CLICKED);
}
/**
* Handles the installer validation callback from Node
* @param {object} statusObj - json containing - {
* valid - (boolean)true for a valid installer, false otherwise,
* installerPath, logFilePath,
* installStatusFilePath - for a valid installer,
* err - for an invalid installer }
*/
function handleValidationStatus(statusObj) {
enableCheckForUpdateEntry(true);
UpdateStatus.cleanUpdateStatus();
if (statusObj.valid) {
// Installer is validated successfully
var statusValidFn = function () {
// Restart button click handler
var restartBtnClicked = function () {
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_RESTART,
"autoUpdate",
"installNotification",
"installNow ",
"click"
);
detachUpdateBarBtnHandlers();
initiateUpdateProcess(statusObj.installerPath, statusObj.logFilePath, statusObj.installStatusFilePath);
};
// Later button click handler
var laterBtnClicked = function () {
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETE_USER_CLICK_LATER,
"autoUpdate",
"installNotification",
"cancel",
"click"
);
detachUpdateBarBtnHandlers();
setUpdateStateInJSON('updateInitiatedInPrevSession', false);
};
//attaching UpdateBar handlers
UpdateInfoBar.on(UpdateInfoBar.RESTART_BTN_CLICKED, restartBtnClicked);
UpdateInfoBar.on(UpdateInfoBar.LATER_BTN_CLICKED, laterBtnClicked);
UpdateInfoBar.showUpdateBar({
title: Strings.DOWNLOAD_COMPLETE,
description: Strings.CLICK_RESTART_TO_UPDATE,
needButtons: true
});
setUpdateStateInJSON("autoUpdateInProgress", false);
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOADCOMPLETE_UPDATE_BAR_RENDERED,
"autoUpdate",
"installNotification",
"render",
""
);
};
if(!isAutoUpdateInitiated) {
isAutoUpdateInitiated = true;
updateJsonHandler.refresh()
.done(function() {
setUpdateStateInJSON('downloadCompleted', true)
.done(statusValidFn);
});
} else {
setUpdateStateInJSON('downloadCompleted', true)
.done(statusValidFn);
}
} else {
// Installer validation failed
if (updateJsonHandler.get("downloadCompleted")) {
// If this was a cached download, retry downloading
updateJsonHandler.reset();
var statusInvalidFn = function () {
downloadAttemptsRemaining = MAX_DOWNLOAD_ATTEMPTS;
getLatestInstaller();
};
setUpdateStateInJSON('downloadCompleted', false)
.done(statusInvalidFn);
} else {
// If this is a new download, prompt the message on update bar
var descriptionMessage = "",
analyticsDescriptionMessage = "";
switch (statusObj.err) {
case _nodeErrorMessages.CHECKSUM_DID_NOT_MATCH:
descriptionMessage = Strings.CHECKSUM_DID_NOT_MATCH;
analyticsDescriptionMessage = "Checksum didn't match.";
break;
case _nodeErrorMessages.INSTALLER_NOT_FOUND:
descriptionMessage = Strings.INSTALLER_NOT_FOUND;
analyticsDescriptionMessage = "Installer not found.";
break;
}
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED,
"autoUpdate",
"download",
"fail",
analyticsDescriptionMessage
);
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.VALIDATION_FAILED,
description: descriptionMessage
});
setUpdateStateInJSON("autoUpdateInProgress", false);
}
}
}
/**
* Handles the download failure callback from Node
* @param {string} message - reason of download failure
*/
function handleDownloadFailure(message) {
console.log("AutoUpdate : Download of latest installer failed in Attempt " +
(MAX_DOWNLOAD_ATTEMPTS - downloadAttemptsRemaining) + ".\n Reason : " + message);
if (downloadAttemptsRemaining) {
// Retry the downloading
attemptToDownload();
} else {
// Download could not completed, all attempts exhausted
enableCheckForUpdateEntry(true);
UpdateStatus.cleanUpdateStatus();
var descriptionMessage = "",
analyticsDescriptionMessage = "";
if (message === _nodeErrorMessages.DOWNLOAD_ERROR) {
descriptionMessage = Strings.DOWNLOAD_ERROR;
analyticsDescriptionMessage = "Error occurred while downloading.";
} else if (message === _nodeErrorMessages.NETWORK_SLOW_OR_DISCONNECTED) {
descriptionMessage = Strings.NETWORK_SLOW_OR_DISCONNECTED;
analyticsDescriptionMessage = "Network is Disconnected or too slow.";
}
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_FAILED,
"autoUpdate",
"download",
"fail",
analyticsDescriptionMessage
);
UpdateInfoBar.showUpdateBar({
type: "error",
title: Strings.DOWNLOAD_FAILED,
description: descriptionMessage
});
setUpdateStateInJSON("autoUpdateInProgress", false);
}
}
/**
* Handles the completion of node state initialization
*/
function handleInitializationComplete() {
enableCheckForUpdateEntry(false);
getLatestInstaller();
}
/**
* Handles Download completion callback from Node
*/
function handleDownloadSuccess() {
HealthLogger.sendAnalyticsData(
autoUpdateEventNames.AUTOUPDATE_DOWNLOAD_COMPLETED,
"autoUpdate",
"download",
"complete",
""
);
UpdateStatus.showUpdateStatus("validating-installer");
validateChecksum();
}
function _handleAppClose() {
postMessageToNode(MessageIds.REMOVE_FROM_REQUESTERS);
}
/**
* Generates a map for brackets side functions
*/
function registerBracketsFunctions() {
functionMap["brackets.notifyinitializationComplete"] = handleInitializationComplete;
functionMap["brackets.showStatusInfo"] = showStatusInfo;
functionMap["brackets.notifyDownloadSuccess"] = handleDownloadSuccess;
functionMap["brackets.showErrorMessage"] = showErrorMessage;
functionMap["brackets.notifyDownloadFailure"] = handleDownloadFailure;
functionMap["brackets.notifySafeToDownload"] = handleSafeToDownload;
functionMap["brackets.notifyvalidationStatus"] = handleValidationStatus;
functionMap["brackets.notifyInstallationStatus"] = handleInstallationStatus;
ProjectManager.on("beforeProjectClose beforeAppClose", _handleAppClose);
}
functionMap["brackets.nodeDomainInitialized"] = nodeDomainInitialized;
functionMap["brackets.registerBracketsFunctions"] = registerBracketsFunctions;
});