├── .gitignore ├── .eslintignore ├── .prettierrc ├── .clasp.json ├── .vscode └── settings.json ├── .eslintrc ├── appsscript.json ├── dist ├── appsscript.json ├── email.html ├── sidebar.html ├── hints.html └── labnol.js ├── .babelrc ├── src ├── html │ ├── email.html │ ├── sidebar.html │ └── hints.html ├── log.js ├── index.js ├── utils.js ├── mail.js ├── server.js ├── connect.js ├── trigger.js ├── analytics.js ├── main.js ├── ui.js ├── cache.js └── props.js ├── LICENSE ├── webpack.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.clasp.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptId": "1vzKEsju2LPmifjGtGbMxokh2eqCviBM3YN8KjuTLHlQB2UQt7DzCkzkN", 3 | "rootDir": "dist" 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.enabled": true, 3 | "eslint.alwaysShowStatus": true, 4 | "editor.formatOnSave": false, 5 | "eslint.autoFixOnSave": true, 6 | "files.autoSave": "onFocusChange", 7 | "eslint.validate": [ 8 | "javascript" 9 | ] 10 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "extends": [ 5 | "eslint:recommended", 6 | "airbnb-base", 7 | "plugin:prettier/recommended" 8 | ], 9 | "plugins": [ 10 | "prettier", 11 | "googleappsscript" 12 | ], 13 | "env": { 14 | "googleappsscript/googleappsscript": true 15 | }, 16 | "rules": { 17 | "prettier/prettier": "error", 18 | "import/prefer-default-export": "error" 19 | } 20 | } -------------------------------------------------------------------------------- /appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "America/Los_Angeles", 3 | "dependencies": { 4 | }, 5 | "exceptionLogging": "STACKDRIVER", 6 | "oauthScopes": [ 7 | "https://www.googleapis.com/auth/spreadsheets.currentonly", 8 | "https://www.googleapis.com/auth/script.container.ui", 9 | "https://www.googleapis.com/auth/script.external_request", 10 | "https://www.googleapis.com/auth/script.scriptapp", 11 | "https://www.googleapis.com/auth/script.send_mail" 12 | ] 13 | } -------------------------------------------------------------------------------- /dist/appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "America/Los_Angeles", 3 | "dependencies": { 4 | }, 5 | "exceptionLogging": "STACKDRIVER", 6 | "oauthScopes": [ 7 | "https://www.googleapis.com/auth/spreadsheets.currentonly", 8 | "https://www.googleapis.com/auth/script.container.ui", 9 | "https://www.googleapis.com/auth/script.external_request", 10 | "https://www.googleapis.com/auth/script.scriptapp", 11 | "https://www.googleapis.com/auth/script.send_mail" 12 | ] 13 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "array-includes", 12 | "@babel/plugin-transform-property-literals", 13 | "@babel/plugin-transform-member-expression-literals", 14 | "@babel/plugin-proposal-class-properties", 15 | "@babel/plugin-proposal-object-rest-spread", 16 | "@babel/plugin-transform-object-assign" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /dist/email.html: -------------------------------------------------------------------------------- 1 |
This email was sent by the Website Monitor app from Digital Inspiration.
3 |If you would like to stop monitoring this website, or require alerts on a different email address, please open the Google Sheet and edit the alert.
4 |For help, please reply to this email or tweet us @labnol.
-------------------------------------------------------------------------------- /src/html/email.html: -------------------------------------------------------------------------------- 1 |This email was sent by the Website Monitor app from Digital Inspiration.
3 |If you would like to stop monitoring this website, or require alerts on a different email address, please open the Google Sheet and edit the alert.
4 |For help, please reply to this email or tweet us @labnol.
-------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | import writeToGoogleAnalytics from './analytics'; 2 | import sendEmailAlert from './mail'; 3 | import { logException } from './utils'; 4 | 5 | const writeToGoogleSheet = message => { 6 | try { 7 | SpreadsheetApp.getActiveSheet().appendRow([new Date(), message]); 8 | } catch (f) { 9 | logException(f); 10 | } 11 | }; 12 | 13 | const logEvent = (settings, status) => { 14 | try { 15 | const { site, ga = '' } = settings; 16 | writeToGoogleSheet([site, 'is', status].join(' ')); 17 | writeToGoogleAnalytics(ga, site, status); 18 | sendEmailAlert(settings, status); 19 | } catch (f) { 20 | logException(f); 21 | } 22 | }; 23 | 24 | export default logEvent; 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Website Monitor 6.0 by Digital Inspiration 4 | ========================================== 5 | 6 | Published by Amit Agarwal on 02/14/2013 7 | Last updated by @labnol on 01/08/2019 8 | 9 | Tutorial: http://labnol.org/?p=21060 10 | 11 | email: amit@labnol.org 12 | twitter: @labnol 13 | 14 | */ 15 | 16 | import { onOpen, showSidebar, removeWebsiteMonitor } from './ui'; 17 | import main from './main'; 18 | import { include } from './utils'; 19 | import { saveSettings } from './server'; 20 | 21 | global.onOpen = onOpen; 22 | global.showSidebar = showSidebar; 23 | global.uninstall = removeWebsiteMonitor; 24 | global.trigger_WebsiteMonitor = main; 25 | global.include = include; 26 | global.saveSettings = saveSettings; 27 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const logException = e => { 2 | console.error(e); 3 | }; 4 | 5 | export const expBackoff = (func, max = 6) => { 6 | for (let n = 0; n < max; n += 1) { 7 | try { 8 | return func(); 9 | } catch (e) { 10 | if (n === max - 1) { 11 | logException(e); 12 | throw e; 13 | } 14 | Utilities.sleep(2 ** n * 1000 + Math.round(Math.random() * 1000)); 15 | } 16 | } 17 | return null; 18 | }; 19 | 20 | export const sleep = (seconds = 1) => { 21 | Utilities.sleep(seconds * 1000); 22 | }; 23 | 24 | export const include = filename => 25 | HtmlService.createHtmlOutputFromFile(filename).getContent(); 26 | 27 | export const TITLE = 'Website Monitor'; 28 | 29 | export const DEVELOPER = 'amit@labnol.org'; 30 | 31 | export const SUCCESS = 200; 32 | -------------------------------------------------------------------------------- /src/mail.js: -------------------------------------------------------------------------------- 1 | import { expBackoff, logException, TITLE, DEVELOPER } from './utils'; 2 | 3 | const sendEmailAlert = (settings, status) => { 4 | try { 5 | const { site, email = '', sheet = '' } = settings; 6 | const subject = `Website ${status} Alert - ${site}`; 7 | const quota = expBackoff(() => MailApp.getRemainingDailyQuota()); 8 | if (quota > 1) { 9 | const html = HtmlService.createTemplateFromFile('email'); 10 | html.site = site; 11 | html.status = status.toLowerCase(); 12 | html.sheet = sheet; 13 | MailApp.sendEmail(email, subject, `${site} is ${status}`, { 14 | htmlBody: html.evaluate().getContent(), 15 | name: TITLE, 16 | replyTo: DEVELOPER 17 | }); 18 | } 19 | } catch (f) { 20 | logException(f); 21 | } 22 | }; 23 | 24 | export default sendEmailAlert; 25 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import properties from './props'; 2 | import { createTrigger } from './trigger'; 3 | import { TITLE, SUCCESS } from './utils'; 4 | 5 | const SETTINGS = 'settings'; 6 | const LAST_STATUS = 'status'; 7 | 8 | export const getSettings = () => 9 | properties.getUserProperty(SETTINGS, true) || {}; 10 | 11 | export const getLastStatus = () => { 12 | const lastStatus = properties.getUserProperty(LAST_STATUS) || SUCCESS; 13 | return +lastStatus; 14 | }; 15 | 16 | export const setLastStatus = status => { 17 | properties.setUserProperty(LAST_STATUS, status); 18 | }; 19 | 20 | export const saveSettings = value => { 21 | properties.setUserProperty(SETTINGS, value); 22 | setLastStatus(SUCCESS); 23 | createTrigger(); 24 | SpreadsheetApp.getActiveSpreadsheet().toast(`${TITLE} is now running!`); 25 | return 'Settings updated!'; 26 | }; 27 | -------------------------------------------------------------------------------- /src/connect.js: -------------------------------------------------------------------------------- 1 | import { logException, SUCCESS } from './utils'; 2 | 3 | const sitecall = func => { 4 | const MAX = 3; 5 | for (let n = 0; n < MAX; n += 1) { 6 | try { 7 | return func(); 8 | } catch (e) { 9 | if (n === MAX - 1) { 10 | logException(e); 11 | throw e; 12 | } 13 | Utilities.sleep(2 ** n * 20000 + Math.round(Math.random() * 1000)); 14 | } 15 | } 16 | return null; 17 | }; 18 | 19 | const getSiteStatus = (url = '') => { 20 | try { 21 | const response = sitecall(() => 22 | UrlFetchApp.fetch(url, { 23 | validateHttpsCertificates: false, 24 | followRedirects: true, 25 | muteHttpExceptions: false 26 | }) 27 | ); 28 | return response.getResponseCode(); 29 | } catch (f) { 30 | logException(f); 31 | return SUCCESS - 1; 32 | } 33 | }; 34 | 35 | export default getSiteStatus; 36 | -------------------------------------------------------------------------------- /src/trigger.js: -------------------------------------------------------------------------------- 1 | import { expBackoff, sleep } from './utils'; 2 | 3 | const TRIGGER = 'trigger_WebsiteMonitor'; 4 | const INTERVAL = 5; 5 | 6 | const toggleTrigger = enableTrigger => { 7 | const triggerList = {}; 8 | 9 | ScriptApp.getProjectTriggers().forEach(trigger => { 10 | if (enableTrigger) { 11 | triggerList[trigger.getHandlerFunction()] = true; 12 | } else { 13 | expBackoff(() => ScriptApp.deleteTrigger(trigger)); 14 | sleep(); 15 | } 16 | }); 17 | 18 | if (enableTrigger) { 19 | if (!triggerList[TRIGGER]) { 20 | ScriptApp.newTrigger(TRIGGER) 21 | .timeBased() 22 | .everyMinutes(INTERVAL) 23 | .create(); 24 | sleep(); 25 | } 26 | } 27 | }; 28 | 29 | export const deleteTrigger = () => { 30 | expBackoff(() => toggleTrigger(false)); 31 | }; 32 | 33 | export const createTrigger = () => { 34 | expBackoff(() => toggleTrigger(true)); 35 | }; 36 | -------------------------------------------------------------------------------- /src/analytics.js: -------------------------------------------------------------------------------- 1 | import { logException, TITLE } from './utils'; 2 | 3 | const s4 = () => 4 | Math.floor((1 + Math.random()) * 0x10000) 5 | .toString(16) 6 | .substring(1); 7 | 8 | const guid = () => 9 | `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; 10 | 11 | const writeToGoogleAnalytics = (id, site, status) => { 12 | if (id) { 13 | try { 14 | const request = [ 15 | 'https://ssl.google-analytics.com/collect?v=1', 16 | 't=event', 17 | `ec=${encodeURIComponent(TITLE)}`, 18 | `tid=${id}`, 19 | `z=${Math.round(new Date().getTime() / 1000)}`, 20 | `cid=${guid()}`, 21 | `ea=${encodeURIComponent(site)}`, 22 | `el=${status}` 23 | ].join('&'); 24 | UrlFetchApp.fetch(request, { muteHttpExceptions: true }); 25 | } catch (e) { 26 | logException(e); 27 | } 28 | } 29 | }; 30 | 31 | export default writeToGoogleAnalytics; 32 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { getSettings, getLastStatus, setLastStatus } from './server'; 2 | import getSiteStatus from './connect'; 3 | import logEvent from './log'; 4 | import { logException, SUCCESS } from './utils'; 5 | 6 | const main = () => { 7 | try { 8 | const settings = getSettings(); 9 | if (settings.site) { 10 | const newStatus = getSiteStatus(settings.site); 11 | const oldStatus = getLastStatus(); 12 | if (oldStatus !== newStatus) { 13 | setLastStatus(newStatus); 14 | if (newStatus === SUCCESS) { 15 | logEvent(settings, 'Up'); 16 | // site is now up 17 | } else if (oldStatus === SUCCESS) { 18 | // site is down 19 | logEvent(settings, 'Down'); 20 | } else { 21 | // site continues to be down 22 | // do nothing 23 | } 24 | } 25 | } 26 | } catch (f) { 27 | logException(f); 28 | } 29 | }; 30 | 31 | export default main; 32 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | import { deleteTrigger } from './trigger'; 2 | import { getSettings } from './server'; 3 | import { TITLE } from './utils'; 4 | 5 | export const onOpen = () => { 6 | const sheet = SpreadsheetApp.getActiveSpreadsheet(); 7 | const menu = [ 8 | { name: 'Configure', functionName: 'showSidebar' }, 9 | null, 10 | { name: '✖ Uninstall', functionName: 'uninstall' } 11 | ]; 12 | 13 | sheet.addMenu(`➪ ${TITLE}`, menu); 14 | }; 15 | 16 | export const showSidebar = () => { 17 | const html = HtmlService.createTemplateFromFile('sidebar'); 18 | const { site = '', email = '', ga = '' } = getSettings(); 19 | html.site = site; 20 | html.email = email; 21 | html.ga = ga; 22 | html.sheet = SpreadsheetApp.getActiveSpreadsheet().getUrl(); 23 | const sidebar = html.evaluate().setTitle(TITLE); 24 | SpreadsheetApp.getUi().showSidebar(sidebar); 25 | }; 26 | 27 | export const removeWebsiteMonitor = () => { 28 | deleteTrigger(); 29 | SpreadsheetApp.getActiveSpreadsheet().toast(`${TITLE} stopped!`); 30 | }; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Amit Agarwal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | import { expBackoff } from './utils'; 2 | 3 | class Cache { 4 | constructor() { 5 | this.userCache = null; 6 | this.docCache = null; 7 | } 8 | 9 | getUserCache() { 10 | if (this.userCache === null) { 11 | try { 12 | this.userCache = expBackoff(() => CacheService.getUserCache()); 13 | } catch (f) { 14 | this.userCache = false; 15 | } 16 | } 17 | return this.userCache; 18 | } 19 | 20 | getUserCacheValue(key, json = false) { 21 | if (!this.getUserCache()) return null; 22 | try { 23 | const value = this.getUserCache().get(key); 24 | if (value) { 25 | return json ? JSON.parse(value) : value; 26 | } 27 | } catch (f) { 28 | // do nothing 29 | } 30 | return null; 31 | } 32 | 33 | setUserCacheValue(key, value, json = false) { 34 | if (!this.getUserCache()) return; 35 | try { 36 | if (!value || (json && !Object.keys(value).length)) { 37 | this.deleteUserCacheValue(key); 38 | return; 39 | } 40 | this.getUserCache().put(key, json ? JSON.stringify(value) : value, 21600); 41 | } catch (f) { 42 | // do nothing 43 | } 44 | } 45 | 46 | deleteUserCacheValue(key) { 47 | if (this.getUserCache()) this.getUserCache().remove(key); 48 | } 49 | } 50 | 51 | const cache = new Cache(); 52 | export default cache; 53 | -------------------------------------------------------------------------------- /src/props.js: -------------------------------------------------------------------------------- 1 | import { expBackoff } from './utils'; 2 | import cache from './cache'; 3 | 4 | class Properties { 5 | constructor() { 6 | this.userProps = null; 7 | } 8 | 9 | getUserProps() { 10 | if (this.userProps === null) { 11 | try { 12 | this.userProps = expBackoff(() => 13 | PropertiesService.getUserProperties() 14 | ); 15 | } catch (f) { 16 | this.userProps = false; 17 | } 18 | } 19 | return this.userProps; 20 | } 21 | 22 | getUserProperty(key, json = false) { 23 | if (!this.getUserProps()) return null; 24 | const value = 25 | cache.getUserCacheValue(`user${key}`) || 26 | this.getUserProps().getProperty(key); 27 | return json ? JSON.parse(value || '{}') : value; 28 | } 29 | 30 | setUserProperty(key, value, json = false) { 31 | if (this.getUserProps()) { 32 | const save = json ? JSON.stringify(value) : value; 33 | cache.setUserCacheValue(`user${key}`, save); 34 | this.getUserProps().setProperty(key, save); 35 | } 36 | } 37 | 38 | deleteUserProperty(key) { 39 | if (this.getUserProps()) { 40 | cache.deleteUserCacheValue(`user${key}`); 41 | this.getUserProps().deleteProperty(key); 42 | } 43 | } 44 | 45 | deleteUserProperties() { 46 | if (this.getUserProps()) { 47 | this.getUserProps().deleteAllProperties(); 48 | } 49 | } 50 | } 51 | 52 | const properties = new Properties(); 53 | export default properties; 54 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const GasPlugin = require('gas-webpack-plugin'); 5 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 6 | 7 | const destination = 'dist'; 8 | 9 | module.exports = { 10 | mode: 'none', 11 | context: __dirname, 12 | entry: './src/index.js', 13 | output: { 14 | filename: `labnol.js`, 15 | path: path.resolve(__dirname, destination), 16 | libraryTarget: 'this' 17 | }, 18 | resolve: { 19 | extensions: ['.js'] 20 | }, 21 | optimization: { 22 | minimizer: [ 23 | new UglifyJSPlugin({ 24 | uglifyOptions: { 25 | ie8: true, 26 | warnings: false, 27 | mangle: false, 28 | compress: { 29 | properties: false, 30 | warnings: false, 31 | drop_console: false 32 | }, 33 | output: { 34 | beautify: true 35 | } 36 | } 37 | }) 38 | ] 39 | }, 40 | module: { 41 | rules: [ 42 | { 43 | enforce: 'pre', 44 | test: /\.js$/, 45 | exclude: /node_modules/, 46 | loader: 'eslint-loader', 47 | options: { 48 | cache: true, 49 | failOnError: false 50 | } 51 | }, 52 | { 53 | test: /\.js$/, 54 | exclude: /node_modules/, 55 | use: { 56 | loader: 'babel-loader' 57 | } 58 | } 59 | ] 60 | }, 61 | plugins: [ 62 | new CleanWebpackPlugin([destination]), 63 | new CopyWebpackPlugin([ 64 | { 65 | from: './src/**/*.html', 66 | flatten: true, 67 | to: path.resolve(__dirname, destination) 68 | }, 69 | { 70 | from: './appsscript.json', 71 | to: path.resolve(__dirname, destination) 72 | } 73 | ]), 74 | new GasPlugin({ 75 | comment: false 76 | }) 77 | ] 78 | }; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-monitor", 3 | "version": "1.1.0", 4 | "description": "Website Uptime Monitor", 5 | "author": { 6 | "name": "Amit Agarwal", 7 | "email": "amit@labnol.org", 8 | "url": "https://digitalinspiration.com" 9 | }, 10 | "homepage": "https://digitalinspiration.com/", 11 | "license": "MIT", 12 | "main": "src/index.js", 13 | "scripts": { 14 | "build": "webpack", 15 | "upload": "clasp push", 16 | "deploy": "npm run build && npm run upload" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/labnol/website-monitor.git" 21 | }, 22 | "keywords": [ 23 | "google-apps-script", 24 | "google-sheets", 25 | "gmail" 26 | ], 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "@babel/core": "^7.2.2", 30 | "@babel/plugin-proposal-class-properties": "^7.3.0", 31 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 32 | "@babel/plugin-transform-member-expression-literals": "^7.2.0", 33 | "@babel/plugin-transform-object-assign": "^7.2.0", 34 | "@babel/plugin-transform-property-literals": "^7.2.0", 35 | "@babel/preset-env": "^7.3.1", 36 | "@google/clasp": "^2.0.1", 37 | "@types/google-apps-script": "^0.0.37", 38 | "babel-eslint": "^10.0.1", 39 | "babel-loader": "^8.0.5", 40 | "babel-plugin-add-module-exports": "^1.0.0", 41 | "babel-plugin-array-includes": "^2.0.3", 42 | "clean-webpack-plugin": "^1.0.1", 43 | "copy-webpack-plugin": "^4.6.0", 44 | "eslint": "^5.13.0", 45 | "eslint-config-airbnb-base": "^13.1.0", 46 | "eslint-config-prettier": "^4.0.0", 47 | "eslint-loader": "^2.1.2", 48 | "eslint-plugin-googleappsscript": "^1.0.1", 49 | "eslint-plugin-import": "^2.16.0", 50 | "eslint-plugin-prettier": "^3.0.1", 51 | "gas-lib": "^2.0.2", 52 | "gas-webpack-plugin": "^1.0.2", 53 | "prettier": "^1.16.4", 54 | "uglifyjs-webpack-plugin": "^2.1.1", 55 | "webpack": "^4.29.3", 56 | "webpack-cli": "^3.2.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Website Monitor for Google Docs 2 | 3 | Setting up website uptime monitor inside Google Sheets is quick and easy. 4 | 5 |  6 | 7 | ## Getting Started 🏃🏼 8 | 9 | 1\. Copy the [Google Sheet](http://bit.ly/Website-Monitor) to your Google Drive. If you using GSuite, please ensure that the Gmail service is enabled for your domain. 10 | 11 | 2\. Go to Website Monitor > Configure and specify the website URL and email address to notify when the website is down or becomes inaccessible. 12 | 13 | 3\. Click Save to start monitoring the uptime and downtime of the website. The email alerts are sent via the Gmail API. 14 | 15 | 4\. (Optional) The Google Script can log the downtime in your Google Analytics. 16 | 17 | ## Developing with Google Apps Script 🚀 18 | 19 | Website Monitor is written in JavaScript (ES6) and compiled to Google Apps Script with Babel and Webpack using the [Apps Script starter kit](https://github.com/labnol/apps-script-starter). 20 | 21 | The starter kit is used for building popular Google add-ons include [Gmail Mail Merge](https://chrome.google.com/webstore/detail/mail-merge-with-attachmen/nifmcbjailaccmombpjjpijjbfoicppp), [Google Forms Notifications](https://chrome.google.com/webstore/detail/email-notifications-for-f/acknfdkglemcidajjmehljifccmflhkm) and [Document Studio](https://chrome.google.com/webstore/detail/document-studio/nhgeilcelhkmajkfgmgldbinmgjjajlb). 22 | 23 | ## Meet the Developer 👨🏼💻 24 | 25 | [Amit Agarwal](https://digitalinspiration.com/google-developer) is a web geek and author of [labnol.org](https://www.labnol.org/), a popular tech how-to website. He frequently uses [Google Apps Script](https://ctrlq.org/) to automate workflows enhance productivity. 26 | 27 | Reach him on [Twitter](https://twitter.com/labnol) or email amit@labnol.org 28 | 29 | ### License 30 | 31 | MIT License (c) [Amit Agarwal](https://digitalinspiration.com/google-developer) 32 | 33 | [](https://github.com/google/clasp) 34 | -------------------------------------------------------------------------------- /dist/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | != include('hints'); ?> 9 | 62 | 120 | 121 | 155 | 158 | 161 | 162 | -------------------------------------------------------------------------------- /src/html/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | != include('hints'); ?> 9 | 62 | 120 | 121 | 155 | 158 | 161 | 162 | -------------------------------------------------------------------------------- /dist/hints.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/html/hints.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/labnol.js: -------------------------------------------------------------------------------- 1 | function onOpen() { 2 | } 3 | function showSidebar() { 4 | } 5 | function uninstall() { 6 | } 7 | function trigger_WebsiteMonitor() { 8 | } 9 | function include() { 10 | } 11 | function saveSettings() { 12 | }(function(e, a) { for(var i in a) e[i] = a[i]; }(this, /******/ (function(modules) { // webpackBootstrap 13 | /******/ // The module cache 14 | /******/ var installedModules = {}; 15 | /******/ 16 | /******/ // The require function 17 | /******/ function __webpack_require__(moduleId) { 18 | /******/ 19 | /******/ // Check if module is in cache 20 | /******/ if(installedModules[moduleId]) { 21 | /******/ return installedModules[moduleId].exports; 22 | /******/ } 23 | /******/ // Create a new module (and put it into the cache) 24 | /******/ var module = installedModules[moduleId] = { 25 | /******/ i: moduleId, 26 | /******/ l: false, 27 | /******/ exports: {} 28 | /******/ }; 29 | /******/ 30 | /******/ // Execute the module function 31 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 32 | /******/ 33 | /******/ // Flag the module as loaded 34 | /******/ module.l = true; 35 | /******/ 36 | /******/ // Return the exports of the module 37 | /******/ return module.exports; 38 | /******/ } 39 | /******/ 40 | /******/ 41 | /******/ // expose the modules object (__webpack_modules__) 42 | /******/ __webpack_require__.m = modules; 43 | /******/ 44 | /******/ // expose the module cache 45 | /******/ __webpack_require__.c = installedModules; 46 | /******/ 47 | /******/ // define getter function for harmony exports 48 | /******/ __webpack_require__.d = function(exports, name, getter) { 49 | /******/ if(!__webpack_require__.o(exports, name)) { 50 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 51 | /******/ } 52 | /******/ }; 53 | /******/ 54 | /******/ // define __esModule on exports 55 | /******/ __webpack_require__.r = function(exports) { 56 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 57 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 58 | /******/ } 59 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 60 | /******/ }; 61 | /******/ 62 | /******/ // create a fake namespace object 63 | /******/ // mode & 1: value is a module id, require it 64 | /******/ // mode & 2: merge all properties of value into the ns 65 | /******/ // mode & 4: return value when already ns object 66 | /******/ // mode & 8|1: behave like require 67 | /******/ __webpack_require__.t = function(value, mode) { 68 | /******/ if(mode & 1) value = __webpack_require__(value); 69 | /******/ if(mode & 8) return value; 70 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 71 | /******/ var ns = Object.create(null); 72 | /******/ __webpack_require__.r(ns); 73 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 74 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 75 | /******/ return ns; 76 | /******/ }; 77 | /******/ 78 | /******/ // getDefaultExport function for compatibility with non-harmony modules 79 | /******/ __webpack_require__.n = function(module) { 80 | /******/ var getter = module && module.__esModule ? 81 | /******/ function getDefault() { return module['default']; } : 82 | /******/ function getModuleExports() { return module; }; 83 | /******/ __webpack_require__.d(getter, 'a', getter); 84 | /******/ return getter; 85 | /******/ }; 86 | /******/ 87 | /******/ // Object.prototype.hasOwnProperty.call 88 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 89 | /******/ 90 | /******/ // __webpack_public_path__ 91 | /******/ __webpack_require__.p = ""; 92 | /******/ 93 | /******/ 94 | /******/ // Load entry module and return exports 95 | /******/ return __webpack_require__(__webpack_require__.s = 0); 96 | /******/ }) 97 | /************************************************************************/ 98 | /******/ ([ 99 | /* 0 */ 100 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 101 | 102 | "use strict"; 103 | __webpack_require__.r(__webpack_exports__); 104 | /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var _ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); 105 | /* harmony import */ var _main__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); 106 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); 107 | /* harmony import */ var _server__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); 108 | /* 109 | 110 | Website Monitor 6.0 by Digital Inspiration 111 | ========================================== 112 | 113 | Published by Amit Agarwal on 02/14/2013 114 | Last updated by @labnol on 01/08/2019 115 | 116 | Tutorial: http://labnol.org/?p=21060 117 | 118 | email: amit@labnol.org 119 | twitter: @labnol 120 | 121 | */ 122 | 123 | 124 | 125 | 126 | global.onOpen = _ui__WEBPACK_IMPORTED_MODULE_0__["onOpen"]; 127 | global.showSidebar = _ui__WEBPACK_IMPORTED_MODULE_0__["showSidebar"]; 128 | global.uninstall = _ui__WEBPACK_IMPORTED_MODULE_0__["removeWebsiteMonitor"]; 129 | global.trigger_WebsiteMonitor = _main__WEBPACK_IMPORTED_MODULE_1__["default"]; 130 | global.include = _utils__WEBPACK_IMPORTED_MODULE_2__["include"]; 131 | global.saveSettings = _server__WEBPACK_IMPORTED_MODULE_3__["saveSettings"]; 132 | /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1))) 133 | 134 | /***/ }), 135 | /* 1 */ 136 | /***/ (function(module, exports) { 137 | 138 | var g; 139 | 140 | // This works in non-strict mode 141 | g = (function() { 142 | return this; 143 | })(); 144 | 145 | try { 146 | // This works if eval is allowed (see CSP) 147 | g = g || new Function("return this")(); 148 | } catch (e) { 149 | // This works if the window reference is available 150 | if (typeof window === "object") g = window; 151 | } 152 | 153 | // g can still be undefined, but nothing to do about it... 154 | // We return undefined, instead of nothing here, so it's 155 | // easier to handle this case. if(!global) { ...} 156 | 157 | module.exports = g; 158 | 159 | 160 | /***/ }), 161 | /* 2 */ 162 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 163 | 164 | "use strict"; 165 | __webpack_require__.r(__webpack_exports__); 166 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onOpen", function() { return onOpen; }); 167 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "showSidebar", function() { return showSidebar; }); 168 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeWebsiteMonitor", function() { return removeWebsiteMonitor; }); 169 | /* harmony import */ var _trigger__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); 170 | /* harmony import */ var _server__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); 171 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); 172 | 173 | 174 | 175 | var onOpen = function onOpen() { 176 | var sheet = SpreadsheetApp.getActiveSpreadsheet(); 177 | var menu = [{ 178 | name: 'Configure', 179 | functionName: 'showSidebar' 180 | }, null, { 181 | name: '✖ Uninstall', 182 | functionName: 'uninstall' 183 | }]; 184 | sheet.addMenu("\u27AA ".concat(_utils__WEBPACK_IMPORTED_MODULE_2__["TITLE"]), menu); 185 | }; 186 | var showSidebar = function showSidebar() { 187 | var html = HtmlService.createTemplateFromFile('sidebar'); 188 | 189 | var _getSettings = Object(_server__WEBPACK_IMPORTED_MODULE_1__["getSettings"])(), 190 | _getSettings$site = _getSettings.site, 191 | site = _getSettings$site === void 0 ? '' : _getSettings$site, 192 | _getSettings$email = _getSettings.email, 193 | email = _getSettings$email === void 0 ? '' : _getSettings$email, 194 | _getSettings$ga = _getSettings.ga, 195 | ga = _getSettings$ga === void 0 ? '' : _getSettings$ga; 196 | 197 | html.site = site; 198 | html.email = email; 199 | html.ga = ga; 200 | html.sheet = SpreadsheetApp.getActiveSpreadsheet().getUrl(); 201 | var sidebar = html.evaluate().setTitle(_utils__WEBPACK_IMPORTED_MODULE_2__["TITLE"]); 202 | SpreadsheetApp.getUi().showSidebar(sidebar); 203 | }; 204 | var removeWebsiteMonitor = function removeWebsiteMonitor() { 205 | Object(_trigger__WEBPACK_IMPORTED_MODULE_0__["deleteTrigger"])(); 206 | SpreadsheetApp.getActiveSpreadsheet().toast("".concat(_utils__WEBPACK_IMPORTED_MODULE_2__["TITLE"], " stopped!")); 207 | }; 208 | 209 | /***/ }), 210 | /* 3 */ 211 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 212 | 213 | "use strict"; 214 | __webpack_require__.r(__webpack_exports__); 215 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "deleteTrigger", function() { return deleteTrigger; }); 216 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "createTrigger", function() { return createTrigger; }); 217 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 218 | 219 | var TRIGGER = 'trigger_WebsiteMonitor'; 220 | var INTERVAL = 5; 221 | 222 | var toggleTrigger = function toggleTrigger(enableTrigger) { 223 | var triggerList = {}; 224 | ScriptApp.getProjectTriggers().forEach(function (trigger) { 225 | if (enableTrigger) { 226 | triggerList[trigger.getHandlerFunction()] = true; 227 | } else { 228 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 229 | return ScriptApp.deleteTrigger(trigger); 230 | }); 231 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["sleep"])(); 232 | } 233 | }); 234 | 235 | if (enableTrigger) { 236 | if (!triggerList[TRIGGER]) { 237 | ScriptApp.newTrigger(TRIGGER).timeBased().everyMinutes(INTERVAL).create(); 238 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["sleep"])(); 239 | } 240 | } 241 | }; 242 | 243 | var deleteTrigger = function deleteTrigger() { 244 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 245 | return toggleTrigger(false); 246 | }); 247 | }; 248 | var createTrigger = function createTrigger() { 249 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 250 | return toggleTrigger(true); 251 | }); 252 | }; 253 | 254 | /***/ }), 255 | /* 4 */ 256 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 257 | 258 | "use strict"; 259 | __webpack_require__.r(__webpack_exports__); 260 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "logException", function() { return logException; }); 261 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "expBackoff", function() { return expBackoff; }); 262 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sleep", function() { return sleep; }); 263 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "include", function() { return include; }); 264 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TITLE", function() { return TITLE; }); 265 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEVELOPER", function() { return DEVELOPER; }); 266 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SUCCESS", function() { return SUCCESS; }); 267 | var logException = function logException(e) { 268 | console.error(e); 269 | }; 270 | var expBackoff = function expBackoff(func) { 271 | var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 6; 272 | 273 | for (var n = 0; n < max; n += 1) { 274 | try { 275 | return func(); 276 | } catch (e) { 277 | if (n === max - 1) { 278 | logException(e); 279 | throw e; 280 | } 281 | 282 | Utilities.sleep(Math.pow(2, n) * 1000 + Math.round(Math.random() * 1000)); 283 | } 284 | } 285 | 286 | return null; 287 | }; 288 | var sleep = function sleep() { 289 | var seconds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; 290 | Utilities.sleep(seconds * 1000); 291 | }; 292 | var include = function include(filename) { 293 | return HtmlService.createHtmlOutputFromFile(filename).getContent(); 294 | }; 295 | var TITLE = 'Website Monitor'; 296 | var DEVELOPER = 'amit@labnol.org'; 297 | var SUCCESS = 200; 298 | 299 | /***/ }), 300 | /* 5 */ 301 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 302 | 303 | "use strict"; 304 | __webpack_require__.r(__webpack_exports__); 305 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getSettings", function() { return getSettings; }); 306 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getLastStatus", function() { return getLastStatus; }); 307 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "setLastStatus", function() { return setLastStatus; }); 308 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "saveSettings", function() { return saveSettings; }); 309 | /* harmony import */ var _props__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); 310 | /* harmony import */ var _trigger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3); 311 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); 312 | 313 | 314 | 315 | var SETTINGS = 'settings'; 316 | var LAST_STATUS = 'status'; 317 | var getSettings = function getSettings() { 318 | return _props__WEBPACK_IMPORTED_MODULE_0__["default"].getUserProperty(SETTINGS, true) || {}; 319 | }; 320 | var getLastStatus = function getLastStatus() { 321 | var lastStatus = _props__WEBPACK_IMPORTED_MODULE_0__["default"].getUserProperty(LAST_STATUS) || _utils__WEBPACK_IMPORTED_MODULE_2__["SUCCESS"]; 322 | return +lastStatus; 323 | }; 324 | var setLastStatus = function setLastStatus(status) { 325 | _props__WEBPACK_IMPORTED_MODULE_0__["default"].setUserProperty(LAST_STATUS, status); 326 | }; 327 | var saveSettings = function saveSettings(value) { 328 | _props__WEBPACK_IMPORTED_MODULE_0__["default"].setUserProperty(SETTINGS, value); 329 | setLastStatus(_utils__WEBPACK_IMPORTED_MODULE_2__["SUCCESS"]); 330 | Object(_trigger__WEBPACK_IMPORTED_MODULE_1__["createTrigger"])(); 331 | SpreadsheetApp.getActiveSpreadsheet().toast("".concat(_utils__WEBPACK_IMPORTED_MODULE_2__["TITLE"], " is now running!")); 332 | return 'Settings updated!'; 333 | }; 334 | 335 | /***/ }), 336 | /* 6 */ 337 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 338 | 339 | "use strict"; 340 | __webpack_require__.r(__webpack_exports__); 341 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 342 | /* harmony import */ var _cache__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7); 343 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 344 | 345 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 346 | 347 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 348 | 349 | 350 | 351 | 352 | var Properties = 353 | /*#__PURE__*/ 354 | function () { 355 | function Properties() { 356 | _classCallCheck(this, Properties); 357 | 358 | this.userProps = null; 359 | } 360 | 361 | _createClass(Properties, [{ 362 | key: "getUserProps", 363 | value: function getUserProps() { 364 | if (this.userProps === null) { 365 | try { 366 | this.userProps = Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 367 | return PropertiesService.getUserProperties(); 368 | }); 369 | } catch (f) { 370 | this.userProps = false; 371 | } 372 | } 373 | 374 | return this.userProps; 375 | } 376 | }, { 377 | key: "getUserProperty", 378 | value: function getUserProperty(key) { 379 | var json = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 380 | if (!this.getUserProps()) return null; 381 | var value = _cache__WEBPACK_IMPORTED_MODULE_1__["default"].getUserCacheValue("user".concat(key)) || this.getUserProps().getProperty(key); 382 | return json ? JSON.parse(value || '{}') : value; 383 | } 384 | }, { 385 | key: "setUserProperty", 386 | value: function setUserProperty(key, value) { 387 | var json = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 388 | 389 | if (this.getUserProps()) { 390 | var save = json ? JSON.stringify(value) : value; 391 | _cache__WEBPACK_IMPORTED_MODULE_1__["default"].setUserCacheValue("user".concat(key), save); 392 | this.getUserProps().setProperty(key, save); 393 | } 394 | } 395 | }, { 396 | key: "deleteUserProperty", 397 | value: function deleteUserProperty(key) { 398 | if (this.getUserProps()) { 399 | _cache__WEBPACK_IMPORTED_MODULE_1__["default"].deleteUserCacheValue("user".concat(key)); 400 | this.getUserProps().deleteProperty(key); 401 | } 402 | } 403 | }, { 404 | key: "deleteUserProperties", 405 | value: function deleteUserProperties() { 406 | if (this.getUserProps()) { 407 | this.getUserProps().deleteAllProperties(); 408 | } 409 | } 410 | }]); 411 | 412 | return Properties; 413 | }(); 414 | 415 | var properties = new Properties(); 416 | /* harmony default export */ __webpack_exports__["default"] = (properties); 417 | 418 | /***/ }), 419 | /* 7 */ 420 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 421 | 422 | "use strict"; 423 | __webpack_require__.r(__webpack_exports__); 424 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 425 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 426 | 427 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 428 | 429 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 430 | 431 | 432 | 433 | var Cache = 434 | /*#__PURE__*/ 435 | function () { 436 | function Cache() { 437 | _classCallCheck(this, Cache); 438 | 439 | this.userCache = null; 440 | this.docCache = null; 441 | } 442 | 443 | _createClass(Cache, [{ 444 | key: "getUserCache", 445 | value: function getUserCache() { 446 | if (this.userCache === null) { 447 | try { 448 | this.userCache = Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 449 | return CacheService.getUserCache(); 450 | }); 451 | } catch (f) { 452 | this.userCache = false; 453 | } 454 | } 455 | 456 | return this.userCache; 457 | } 458 | }, { 459 | key: "getUserCacheValue", 460 | value: function getUserCacheValue(key) { 461 | var json = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 462 | if (!this.getUserCache()) return null; 463 | 464 | try { 465 | var value = this.getUserCache().get(key); 466 | 467 | if (value) { 468 | return json ? JSON.parse(value) : value; 469 | } 470 | } catch (f) {// do nothing 471 | } 472 | 473 | return null; 474 | } 475 | }, { 476 | key: "setUserCacheValue", 477 | value: function setUserCacheValue(key, value) { 478 | var json = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 479 | if (!this.getUserCache()) return; 480 | 481 | try { 482 | if (!value || json && !Object.keys(value).length) { 483 | this.deleteUserCacheValue(key); 484 | return; 485 | } 486 | 487 | this.getUserCache().put(key, json ? JSON.stringify(value) : value, 21600); 488 | } catch (f) {// do nothing 489 | } 490 | } 491 | }, { 492 | key: "deleteUserCacheValue", 493 | value: function deleteUserCacheValue(key) { 494 | if (this.getUserCache()) this.getUserCache().remove(key); 495 | } 496 | }]); 497 | 498 | return Cache; 499 | }(); 500 | 501 | var cache = new Cache(); 502 | /* harmony default export */ __webpack_exports__["default"] = (cache); 503 | 504 | /***/ }), 505 | /* 8 */ 506 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 507 | 508 | "use strict"; 509 | __webpack_require__.r(__webpack_exports__); 510 | /* harmony import */ var _server__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); 511 | /* harmony import */ var _connect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); 512 | /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10); 513 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); 514 | 515 | 516 | 517 | 518 | 519 | var main = function main() { 520 | try { 521 | var settings = Object(_server__WEBPACK_IMPORTED_MODULE_0__["getSettings"])(); 522 | 523 | if (settings.site) { 524 | var newStatus = Object(_connect__WEBPACK_IMPORTED_MODULE_1__["default"])(settings.site); 525 | var oldStatus = Object(_server__WEBPACK_IMPORTED_MODULE_0__["getLastStatus"])(); 526 | 527 | if (oldStatus !== newStatus) { 528 | Object(_server__WEBPACK_IMPORTED_MODULE_0__["setLastStatus"])(newStatus); 529 | 530 | if (newStatus === _utils__WEBPACK_IMPORTED_MODULE_3__["SUCCESS"]) { 531 | Object(_log__WEBPACK_IMPORTED_MODULE_2__["default"])(settings, 'Up'); // site is now up 532 | } else if (oldStatus === _utils__WEBPACK_IMPORTED_MODULE_3__["SUCCESS"]) { 533 | // site is down 534 | Object(_log__WEBPACK_IMPORTED_MODULE_2__["default"])(settings, 'Down'); 535 | } else {// site continues to be down 536 | // do nothing 537 | } 538 | } 539 | } 540 | } catch (f) { 541 | Object(_utils__WEBPACK_IMPORTED_MODULE_3__["logException"])(f); 542 | } 543 | }; 544 | 545 | /* harmony default export */ __webpack_exports__["default"] = (main); 546 | 547 | /***/ }), 548 | /* 9 */ 549 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 550 | 551 | "use strict"; 552 | __webpack_require__.r(__webpack_exports__); 553 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 554 | 555 | 556 | var sitecall = function sitecall(func) { 557 | var MAX = 3; 558 | 559 | for (var n = 0; n < MAX; n += 1) { 560 | try { 561 | return func(); 562 | } catch (e) { 563 | if (n === MAX - 1) { 564 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["logException"])(e); 565 | throw e; 566 | } 567 | 568 | Utilities.sleep(Math.pow(2, n) * 20000 + Math.round(Math.random() * 1000)); 569 | } 570 | } 571 | 572 | return null; 573 | }; 574 | 575 | var getSiteStatus = function getSiteStatus() { 576 | var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 577 | 578 | try { 579 | var response = sitecall(function () { 580 | return UrlFetchApp.fetch(url, { 581 | validateHttpsCertificates: false, 582 | followRedirects: true, 583 | muteHttpExceptions: false 584 | }); 585 | }); 586 | return response.getResponseCode(); 587 | } catch (f) { 588 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["logException"])(f); 589 | return _utils__WEBPACK_IMPORTED_MODULE_0__["SUCCESS"] - 1; 590 | } 591 | }; 592 | 593 | /* harmony default export */ __webpack_exports__["default"] = (getSiteStatus); 594 | 595 | /***/ }), 596 | /* 10 */ 597 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 598 | 599 | "use strict"; 600 | __webpack_require__.r(__webpack_exports__); 601 | /* harmony import */ var _analytics__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11); 602 | /* harmony import */ var _mail__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12); 603 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); 604 | 605 | 606 | 607 | 608 | var writeToGoogleSheet = function writeToGoogleSheet(message) { 609 | try { 610 | SpreadsheetApp.getActiveSheet().appendRow([new Date(), message]); 611 | } catch (f) { 612 | Object(_utils__WEBPACK_IMPORTED_MODULE_2__["logException"])(f); 613 | } 614 | }; 615 | 616 | var logEvent = function logEvent(settings, status) { 617 | try { 618 | var site = settings.site, 619 | _settings$ga = settings.ga, 620 | ga = _settings$ga === void 0 ? '' : _settings$ga; 621 | writeToGoogleSheet([site, 'is', status].join(' ')); 622 | Object(_analytics__WEBPACK_IMPORTED_MODULE_0__["default"])(ga, site, status); 623 | Object(_mail__WEBPACK_IMPORTED_MODULE_1__["default"])(settings, status); 624 | } catch (f) { 625 | Object(_utils__WEBPACK_IMPORTED_MODULE_2__["logException"])(f); 626 | } 627 | }; 628 | 629 | /* harmony default export */ __webpack_exports__["default"] = (logEvent); 630 | 631 | /***/ }), 632 | /* 11 */ 633 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 634 | 635 | "use strict"; 636 | __webpack_require__.r(__webpack_exports__); 637 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 638 | 639 | 640 | var s4 = function s4() { 641 | return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 642 | }; 643 | 644 | var guid = function guid() { 645 | return "".concat(s4()).concat(s4(), "-").concat(s4(), "-").concat(s4(), "-").concat(s4(), "-").concat(s4()).concat(s4()).concat(s4()); 646 | }; 647 | 648 | var writeToGoogleAnalytics = function writeToGoogleAnalytics(id, site, status) { 649 | if (id) { 650 | try { 651 | var request = ['https://ssl.google-analytics.com/collect?v=1', 't=event', "ec=".concat(encodeURIComponent(_utils__WEBPACK_IMPORTED_MODULE_0__["TITLE"])), "tid=".concat(id), "z=".concat(Math.round(new Date().getTime() / 1000)), "cid=".concat(guid()), "ea=".concat(encodeURIComponent(site)), "el=".concat(status)].join('&'); 652 | UrlFetchApp.fetch(request, { 653 | muteHttpExceptions: true 654 | }); 655 | } catch (e) { 656 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["logException"])(e); 657 | } 658 | } 659 | }; 660 | 661 | /* harmony default export */ __webpack_exports__["default"] = (writeToGoogleAnalytics); 662 | 663 | /***/ }), 664 | /* 12 */ 665 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 666 | 667 | "use strict"; 668 | __webpack_require__.r(__webpack_exports__); 669 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); 670 | 671 | 672 | var sendEmailAlert = function sendEmailAlert(settings, status) { 673 | try { 674 | var site = settings.site, 675 | _settings$email = settings.email, 676 | email = _settings$email === void 0 ? '' : _settings$email, 677 | _settings$sheet = settings.sheet, 678 | sheet = _settings$sheet === void 0 ? '' : _settings$sheet; 679 | var subject = "Website ".concat(status, " Alert - ").concat(site); 680 | var quota = Object(_utils__WEBPACK_IMPORTED_MODULE_0__["expBackoff"])(function () { 681 | return MailApp.getRemainingDailyQuota(); 682 | }); 683 | 684 | if (quota > 1) { 685 | var html = HtmlService.createTemplateFromFile('email'); 686 | html.site = site; 687 | html.status = status.toLowerCase(); 688 | html.sheet = sheet; 689 | MailApp.sendEmail(email, subject, "".concat(site, " is ").concat(status), { 690 | htmlBody: html.evaluate().getContent(), 691 | name: _utils__WEBPACK_IMPORTED_MODULE_0__["TITLE"], 692 | replyTo: _utils__WEBPACK_IMPORTED_MODULE_0__["DEVELOPER"] 693 | }); 694 | } 695 | } catch (f) { 696 | Object(_utils__WEBPACK_IMPORTED_MODULE_0__["logException"])(f); 697 | } 698 | }; 699 | 700 | /* harmony default export */ __webpack_exports__["default"] = (sendEmailAlert); 701 | 702 | /***/ }) 703 | /******/ ]))); --------------------------------------------------------------------------------