diff --git a/.eslintrc.js b/.eslintrc.js index 13a5fa2..590b8f0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file ESLint setup - * @author TheJaredWilcurt + * @file ESLint setup */ const path = require('path'); diff --git a/api-type-definitions.js b/api-type-definitions.js index f9a8be7..903488c 100644 --- a/api-type-definitions.js +++ b/api-type-definitions.js @@ -1,6 +1,5 @@ /** - * @file Type definitions for the API and reusable functions/objects - * @author TheJaredWilcurt + * @file Type definitions for the API and reusable functions/objects. */ /** diff --git a/index.js b/index.js index bacda0d..c4e66d2 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,8 @@ 'use strict'; /** - * @file Entry point for the library. Exposes the external facing function that accepts the input defined in the API documentation. - * @author TheJaredWilcurt + * @file Entry point for the library. Exposes the external facing function that + * accepts the input defined in the API documentation. */ /* eslint-disable */ @@ -11,26 +11,56 @@ const { OPTIONS } = require('./api-type-definitions.js'); const validation = require('./src/validation.js'); const getVersionUrl = require('./src/getVersionUrl.js'); +const getLatestLocal = require('./src/getLatestLocal.js'); + +function stub () {} const nwSplasherAutoUpdate = { /** - * [description]. + * Checks for updates. Downloads zip and extracts it if + * new version is available. Launches a window pointed + * to the latest version. * - * @param {OPTIONS} options [description]. + * @param {OPTIONS} options The user's options object */ downloadLatestAppAndOpenWindowInBackground: async function (options) { + // Validate options options = validation.validateDownloadLatestAppAndOpenWindowInBackgroundOptions(options); - const data = await getVersionUrl(options); - console.log({ data }); + + // Get remote version data + const versionUrlResponse = await getVersionUrl(options); + + // Get latest local + const latestLocal = await getLatestLocal(options); + // confirm version + const latestRemote = await options.autoUpdate.confirmNewVersion(versionUrlResponse, latestLocal); + + console.log({ latestRemote, latestLocal }); + // get download path + stub(); + // download zip + stub(); + // validate zip + stub(); + // extract zip + stub(); + // validate extraction + stub(); + // Update/Retry/Error/Complete + stub(); + // close splash + stub(); + // open app window + stub(); }, setCurrentWorkingDirectory: function () {}, closeSplashAndShowApp: function (options) { diff --git a/manual-testing.js b/manual-testing.js index d231ded..ed833ce 100644 --- a/manual-testing.js +++ b/manual-testing.js @@ -1,14 +1,13 @@ 'use strict'; /** - * @file Used for manually testing the code - * @author TheJaredWilcurt + * @file Used for manually testing the code */ const library = require('./index.js'); const semver = require('semver'); -const options = { +let options = { autoUpdate: { versionUrl: 'https://api.github.com/repos/scout-app/scout-app/releases', confirmNewVersion: function (response, latestLocal) { @@ -27,4 +26,162 @@ const options = { } }; +options = { + // OPTIONAL: defaults to true + verbose: true, + /** + * OPTIONAL: console.error is called by default if verbose: true. + * + * Your own custom logging function called with helpful warning/error + * messages from the internal validators. Only used if verbose: true. + * + * @param {string} message The human readable warning/error message + * @param {object} error Sometimes an error or options object is passed + */ + customLogger: function (message, error) { + console.log('customLogger', message, error); + }, + splasher: { + /** + * The websocket port the splash screen window listens on. When your main app window + * loads, it will use the same port to signal the splash screen to close itself. + */ + port: 4443, + /** + * Time in ms to wait after the new window is launched before auto-closing + * splash screen if a signal is not recieved to close sooner. Send -1 to + * only close if signaled via the websocket port. + */ + closeSplashAfter: 3000 + }, + autoUpdate: { + /** + * NW.js Splasher Auto Update will make a network request to the versionUrl, + * then pass the response into the confirmNewVersion and downloadPath + * callback functions you provide. + */ + // This is an example, you can put whatever URL you want here + versionUrl: 'https://api.github.com/repos/scout-app/scout-app/releases', + /** + * Check if the latest remote version is newer than the latest local version. + * If a new version is available, return the new version number to begin the + * download/extract. If no new version exists, then return false and the latest + * local version will be opened in a new window and the splash screen closed. + * + * @param {string} response The network respone from versionUrl + * @param {string} latestLocal The latest downloaded version, or undefined if not present + * @return {string} The new version number (continue to download zip), or false (open current version) + */ + confirmNewVersion: function (response, latestLocal) { + console.log('confirmNewVersion'); + const latestRemote = response.data[0].tag_name.replace('v', ''); + const updateAvailable = semver.gt(latestRemote, latestLocal); + if (updateAvailable) { + return latestRemote; + } + return false; + }, + /** + * Called after hitting the versionUrl with the response. + * Must return a url to a ZIP file to be downloaded/extracted + * + * @param {string} response The response body from the network request + * @return {string} A url to a ZIP file to be downloaded + */ + downloadPath: function (response) { + console.log('downloadPath'); + //This is just an example, you can put any logic you want here + response = JSON.parse(response); + return response.latest.downloadUrl; + }, + // If the download or extract fails, we will retry n times before stopping + downloadRetries: 3, + extractRetries: 3, + /** + * Called when an update occurs during download/extract. + * + * @param {object} update Object containing percents + * @param {number} update.downloadProgress The download progress percent + * @param {number} update.extractProgress The extract progress percent + */ + onUpdate: function ({ downloadProgress, extractProgress }) { + console.log('onUpdate'); + //This is just an example, you can put any logic you want here + if (downloadProgress) { + console.log('Download progress: ' + downloadProgress + '%'); + } + if (extractProgress) { + console.log('Unzipping: ' + downloadProgress + '%'); + } + }, + /** + * Optional function. You can run any code to validate + * that the downloaded zip matches expecations. + * If it does return true. If you return false, then + * nwSplasherAutoUpdate will retry or stop running. + * + * @param {string} pathToZip File path to the downloaded zip file + * @return {Boolean} true = continue, false = retry/stop + */ + validateZip: function (pathToZip) { + console.log('validateZip'); + //This is just an example, you can put any logic you want here + return true; + }, + /** + * Optional function. You can run any code to validate + * that the files extracted from the zip match your + * expecations. If they do return true. If you return false, + * then nwSplasherAutoUpdate will retry or stop running. + * + * @param {string} pathToExtract File path to the downloaded zip file + * @return {Boolean} true = continue, false = retry/stop + */ + validateExtract: function (pathToExtract) { + console.log('validateExtract'); + //This is just an example, you can put any logic you want here + return true; + }, + /** + * When download or extract fails, but we haven't + * exhausted all retries yet, this is called. + * + * @param {string} message Human readable warning message + */ + onRetry: function (message) { + //This is just an example, you can put any logic you want here + console.log('onRetry', message); + }, + /** + * Called when an error is encountered that ended execution + * prematurely. Such as failing to download or extract after + * all retries were exhausted. + * + * @param {string} errorMessage Human readable error message + * @param {object} error Detailed error information if available + */ + onError: function (errorMessage, error) { + //This is just an example, you can put any logic you want here + console.log('onError', errorMessage, error); + }, + /** + * Called just prior to opening the new window + * and closing the splash screen. + */ + onComplete: function () { + console.log('onComplete'); + } + }, + newWindow: { + // The file from the extracted zip to load in the new window + entry: 'index.html', + // Any NW.js window subfield options: docs.nwjs.io/en/latest/References/Manifest%20Format/#window-subfields + window: { + min_width: 400, + min_height: 250 + } + } +}; + + library.downloadLatestAppAndOpenWindowInBackground(options); diff --git a/src/constants.js b/src/constants.js index 4f09c9b..e450419 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file Defines shared constant variables - * @author TheJaredWilcurt + * @file Defines shared constant variables */ const DEFAULT_CLOSE_SPLASH_AFTER = 3000; diff --git a/src/getLatestLocal.js b/src/getLatestLocal.js new file mode 100644 index 0000000..a602f6a --- /dev/null +++ b/src/getLatestLocal.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * @file Checks the most recent local version of the app + */ + +const { OPTIONS } = require('../api-type-definitions.js'); + +const helpers = require('./helpers.js'); + +/** + * + * + * @param {OPTIONS} options User's validated options + * @return {Promise} . + */ +async function getLatestLocal (options) { + return new Promise((resolve, reject) => { + let latestLocal = '0.0.0'; + if (latestLocal) { + resolve(latestLocal); + } else { + helpers.throwError(options, 'Error getting latest local version number', error); + reject(); + } + }); +}; + +module.exports = getLatestLocal; diff --git a/src/getVersionUrl.js b/src/getVersionUrl.js index 679a273..736ebe6 100644 --- a/src/getVersionUrl.js +++ b/src/getVersionUrl.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file Hits a provided endpoint and returns the data containing version info - * @author TheJaredWilcurt + * @file Hits a provided endpoint and returns the data containing version info */ const { OPTIONS } = require('../api-type-definitions.js'); diff --git a/src/helpers.js b/src/helpers.js index 7cd2f3d..1f74eaf 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file File contains helper functions used by different files in the library. - * @author TheJaredWilcurt + * @file File contains helper functions used by different files in the library. */ const helpers = { diff --git a/src/validation.js b/src/validation.js index 9122686..a1a09ca 100644 --- a/src/validation.js +++ b/src/validation.js @@ -1,8 +1,9 @@ 'use strict'; /** - * @file This file validates the options object passed in by the user of this library to ensure it meets the expectations of the code. - * @author TheJaredWilcurt + * @file This file validates the options object passed in by the user of + * this library to ensure it meets the expectations of the code. + * It also provides default values for options where possible. */ const { diff --git a/tests/setup.js b/tests/setup.js index 1d2ed8b..a6dc4ee 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,6 +1,5 @@ 'use strict'; /** - * @file Vitest setup file - * @author TheJaredWilcurt + * @file Vitest setup file */ diff --git a/tests/src/validation.test.js b/tests/src/validation.test.js index 2452cd9..d56201a 100644 --- a/tests/src/validation.test.js +++ b/tests/src/validation.test.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file Tests the validation file's functions - * @author TheJaredWilcurt + * @file Tests the validation file's functions */ const helpers = require('../../src/helpers.js'); diff --git a/vite.config.js b/vite.config.js index 1b07ec2..f72b4a7 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,7 @@ 'use strict'; /** - * @file Vite config - * @author TheJaredWilcurt + * @file Configuration file for Vite to set up unit tests. */ /* eslint-disable import/extensions,import/no-unresolved,import/no-anonymous-default-export,import/no-unused-modules */