From fc40b921ca57f00fb0e464b60c33b7525724b40a Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 10 Nov 2016 17:07:45 -0600 Subject: [PATCH] feat(error): client runtime error reporting --- bin/ion-dev.js | 245 ++++++++++++++------------ src/dev-server/notification-server.ts | 79 +++++---- src/util/logger-diagnostics.ts | 172 +++++++++++++++--- 3 files changed, 330 insertions(+), 166 deletions(-) diff --git a/bin/ion-dev.js b/bin/ion-dev.js index 90829eac..0579d32e 100644 --- a/bin/ion-dev.js +++ b/bin/ion-dev.js @@ -1,4 +1,5 @@ window.IonicDevServerConfig = window.IonicDevServerConfig || {}; + window.IonicDevServer = { start: function() { this.msgQueue = []; @@ -15,132 +16,94 @@ window.IonicDevServer = { } this.openConnection(); - this.bindKeyboardEvents(); - document.addEventListener("DOMContentLoaded", IonicDevServer.domReady); - }, - - domReady: function() { - document.removeEventListener("DOMContentLoaded", IonicDevServer.domReady); - var diagnosticsEle = document.getElementById('ion-diagnostics'); - if (diagnosticsEle) { - IonicDevServer.buildStatus('error'); - } else { - IonicDevServer.buildStatus('success'); - } - }, - - handleError: function(err) { - var self = this; - - console.error('Handling error', err); - - var existing = document.querySelector('._ionic-error-view'); - if(existing) { - document.body.removeChild(existing); - } - - this._errorWindow = this._makeErrorWindow(err); - document.body.appendChild(this._errorWindow); - - setTimeout(function() { - window.requestAnimationFrame(function() { - self._errorWindow.classList.add('show'); - }); - }, 500); - - }, - - _closeErrorWindow: function() { var self = this; - window.requestAnimationFrame(function() { - self._errorWindow.classList.remove('show'); - setTimeout(function() { - document.body.removeChild(self._errorWindow); - self._errorWindow = null; - }, 500); + document.addEventListener("DOMContentLoaded", function() { + var diagnosticsEle = document.getElementById('ion-diagnostics'); + if (diagnosticsEle) { + self.buildStatus('error'); + } else { + self.buildStatus('success'); + } }); }, - _makeErrorWindow: function(err) { - var self = this; - - var isInCordova = !!window.cordova; + handleError: function(err) { + if (!err) return; - var d = document.createElement('div'); - d.className = '_ionic-error-view'; + if (this.socketReady) { + var msg = { + category: 'runtimeError', + type: 'runtimeError', + data: { + message: err.message ? err.message.toString() : null, + stack: err.stack ? err.stack.toString() : null + } + }; + this.queueMessageSend(msg); - if(isInCordova) { - d.classList.add('_ionic-error-in-cordova'); + } else { + var c = []; + + c.push('
'); + c.push('
Error
'); + c.push('
'); + c.push(''); + c.push('
'); + c.push('
'); + + c.push('
'); + c.push('
'); + c.push('
Runtime Error
'); + c.push('
' + err.message + '
'); + c.push('
'); + c.push('
Stack
'); + c.push('
' + err.stack + '
'); + c.push('
'); + + this.buildUpdate({ + type: 'clientError', + data: { + diagnosticsHtml: c.join('') + } + }); } - - d.innerHTML = '

App Runtime Error

Close
' + err.message + '

Stacktrace

' + this._makeErrorButtonsHtml() + '
'; - - d.querySelector('._close').addEventListener('click', function(e) { - closeWindow(d); - }); - /* - d.querySelector('[action="copy"]').addEventListener('click', function(e) { - if(window.IonicDevtools) { - window.IonicDevtools.copyErrorToClipboard(err); - } - }); - */ - - return d; }, - _makeErrorButtonsHtml: function() { - var d = document.createElement('div'); - d.className = '_ion-error-buttons'; - - var b1 = document.createElement('button'); - b1.innerHTML = 'Close (ESC)'; - b1.className = '_button'; - - var b2 = document.createElement('button'); - b2.innerHTML = 'Reload (⌘)'; - b2.className = '_button'; - //d.appendChild(b1); - //d.appendChild(b2); - - return d.innerHTML; - }, reloadApp: function() { - if(window.cordova) { - window.location.reload(true); - } - }, - showDebugMenu: function() { - if(window.IonicDevtools) { - window.IonicDevtools.showDebugMenu(); - } + window.location.reload(true); }, + bindKeyboardEvents: function() { var self = this; - document.addEventListener('keyup', function(event) { - var key = event.keyCode || event.charCode || 0; + document.addEventListener('keyup', function(ev) { + var key = ev.keyCode || ev.charCode || 0; - if(key == 27 && self._errorWindow) { + if (key == 27 && self._errorWindow) { self._closeErrorWindow(); } }); - document.addEventListener('keydown', function(event) { - var key = event.keyCode || event.charCode || 0; + + document.addEventListener('keydown', function(ev) { + var key = ev.keyCode || ev.charCode || 0; // Check for reload command (cmd/ctrl+R) - if(key == 82 && (event.metaKey || event.ctrlKey)) { + if (key == 82 && (ev.metaKey || ev.ctrlKey)) { self.reloadApp(); } + }); - // Check for debugger command (cmd/ctrl+D) - /* - if(key == 68 && (event.metaKey || event.ctrlKey)) { - self.showDebugMenu(); + document.addEventListener('click', function(ev) { + if (ev.target && ev.target.classList.contains('ion-diagnostic-close')) { + self.buildUpdate({ + type: 'closeDiagnostics', + data: { + diagnosticsHtml: null + } + }); } - */ }); }, @@ -164,8 +127,9 @@ window.IonicDevServer = { } }; - self.socket.onclose = () => { + self.socket.onclose = function() { self.consoleLog('Dev server logger closed'); + self.socketReady = false; }; self.drainMessageQueue(); @@ -184,7 +148,7 @@ window.IonicDevServer = { try { this.socket.send(JSON.stringify(msg)); } catch(e) { - if(e instanceof TypeError) { + if (e instanceof TypeError) { } else { this.consoleError('ws error: ' + e); @@ -243,7 +207,8 @@ window.IonicDevServer = { toastEle.innerHTML = c.join(''); document.body.insertBefore(toastEle, document.body.firstChild); } - IonicDevServer.toastTimerId = setTimeout(function() { + + this.toastTimerId = setTimeout(function() { var toastEle = document.getElementById('ion-diagnostics-toast'); if (toastEle) { toastEle.classList.add('ion-diagnostics-toast-active'); @@ -253,7 +218,7 @@ window.IonicDevServer = { } else { status = msg.data.diagnosticsHtml ? 'error' : 'success'; - clearTimeout(IonicDevServer.toastTimerId); + clearTimeout(this.toastTimerId); var toastEle = document.getElementById('ion-diagnostics-toast'); if (toastEle) { @@ -263,7 +228,8 @@ window.IonicDevServer = { var diagnosticsEle = document.getElementById('ion-diagnostics'); if (diagnosticsEle && !msg.data.diagnosticsHtml) { diagnosticsEle.classList.add('ion-diagnostics-fade-out'); - IonicDevServer.diagnosticsTimerId = setTimeout(function() { + + this.diagnosticsTimerId = setTimeout(function() { var diagnosticsEle = document.getElementById('ion-diagnostics'); if (diagnosticsEle) { diagnosticsEle.parentElement.removeChild(diagnosticsEle); @@ -271,25 +237,27 @@ window.IonicDevServer = { }, 100); } else if (msg.data.diagnosticsHtml) { - clearTimeout(IonicDevServer.diagnosticsTimerId); + + clearTimeout(this.diagnosticsTimerId); if (!diagnosticsEle) { diagnosticsEle = document.createElement('div'); diagnosticsEle.id = 'ion-diagnostics'; diagnosticsEle.className = 'ion-diagnostics-fade-out'; document.body.insertBefore(diagnosticsEle, document.body.firstChild); - IonicDevServer.diagnosticsTimerId = setTimeout(function() { + + this.diagnosticsTimerId = setTimeout(function() { var diagnosticsEle = document.getElementById('ion-diagnostics'); if (diagnosticsEle) { diagnosticsEle.classList.remove('ion-diagnostics-fade-out'); } }, 24); } - diagnosticsEle.innerHTML = msg.data.diagnosticsHtml; + diagnosticsEle.innerHTML = msg.data.diagnosticsHtml } } - IonicDevServer.buildStatus(status); + this.buildStatus(status); }, buildStatus: function (status) { @@ -301,7 +269,7 @@ window.IonicDevServer = { var iconLink = document.createElement('link'); iconLink.rel = 'icon'; iconLink.type = 'image/png'; - iconLink.href = IonicDevServer[status + 'Icon']; + iconLink.href = this[status + 'Icon']; document.head.appendChild(iconLink); if (status === 'error') { @@ -309,7 +277,7 @@ window.IonicDevServer = { if (diagnosticsEle) { var systemInfoEle = diagnosticsEle.querySelector('#ion-diagnostics-system-info'); if (!systemInfoEle) { - systemInfoEle = document.createElement('pre'); + systemInfoEle = document.createElement('div'); systemInfoEle.id = 'ion-diagnostics-system-info'; systemInfoEle.innerHTML = IonicDevServerConfig.systemInfo.join('\n'); diagnosticsEle.appendChild(systemInfoEle); @@ -318,6 +286,61 @@ window.IonicDevServer = { } }, + showOptions: function() { + + }, + + enableShake: function() { + /* + * Author: Alex Gibson + * https://github.com/alexgibson/shake.js + * License: MIT license + */ + var self = this; + var threshold = 15; + var timeout = 1000; + + self.shakeTime = new Date(); + self.shakeX = null; + self.shakeY = null; + self.shakeZ = null; + + window.addEventListener('devicemotion', function(ev) { + var current = ev.accelerationIncludingGravity; + var currentTime; + var timeDifference; + var deltaX = 0; + var deltaY = 0; + var deltaZ = 0; + + if (self.shakeX === null) { + self.shakeX = current.x; + self.shakeY = current.y; + self.shakeZ = current.z; + return; + } + + deltaX = Math.abs(self.shakeX - current.x); + deltaY = Math.abs(self.shakeY - current.y); + deltaZ = Math.abs(self.shakeZ - current.z); + + if (((deltaX > threshold) && (deltaY > threshold)) || ((deltaX > threshold) && (deltaZ > threshold)) || ((deltaY > threshold) && (deltaZ > threshold))) { + currentTime = new Date(); + timeDifference = currentTime.getTime() - self.shakeTime.getTime(); + + if (timeDifference > timeout) { + self.showOptions(); + self.shakeTime = new Date(); + } + } + + self.shakeX = current.x; + self.shakeY = current.y; + self.shakeZ = current.z; + }); + + }, + activeIcon: 'data:image/png;base64,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', errorIcon: 'data:image/png;base64,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', diff --git a/src/dev-server/notification-server.ts b/src/dev-server/notification-server.ts index a8a1542f..465668f6 100644 --- a/src/dev-server/notification-server.ts +++ b/src/dev-server/notification-server.ts @@ -1,6 +1,6 @@ // Ionic Dev Server: Server Side Logger import { Logger } from '../util/logger'; -import { hasDiagnostics, getDiagnosticsHtmlContent } from '../util/logger-diagnostics'; +import { hasDiagnostics, getDiagnosticsHtmlContent, generateRuntimeDiagnosticContent } from '../util/logger-diagnostics'; import { on, EventType } from '../util/events'; import { Server as WebSocketServer } from 'ws'; import { ServeConfig } from './serve-config'; @@ -8,6 +8,7 @@ import { ServeConfig } from './serve-config'; export function createNotificationServer(config: ServeConfig) { let wsServer: any; + const msgToClient: WsMessage[] = []; // queue up all messages to the client function queueMessageSend(msg: WsMessage) { @@ -75,51 +76,61 @@ export function createNotificationServer(config: ServeConfig) { drainMessageQueue(); }); -} - -function printMessageFromClient(msg: WsMessage) { - if (msg.data) { - switch (msg.category) { - case 'console': - printConsole(msg); - break; + function printMessageFromClient(msg: WsMessage) { + if (msg && msg.data) { + switch (msg.category) { + case 'console': + printConsole(msg); + break; - case 'exception': - printException(msg); - break; + case 'runtimeError': + handleRuntimeError(msg); + break; + } } } -} -function printConsole(msg: WsMessage) { - const args = msg.data; - args[0] = `console.${msg.type}: ${args[0]}`; - switch (msg.type) { - case 'error': - Logger.error.apply(this, args); - break; + function printConsole(msg: WsMessage) { + const args = msg.data; + args[0] = `console.${msg.type}: ${args[0]}`; - case 'warn': - Logger.warn.apply(this, args); - break; + switch (msg.type) { + case 'error': + Logger.error.apply(this, args); + break; - case 'debug': - Logger.debug.apply(this, args); - break; + case 'warn': + Logger.warn.apply(this, args); + break; - default: - Logger.info.apply(this, args); - break; - } -} + case 'debug': + Logger.debug.apply(this, args); + break; -function printException(msg: WsMessage) { + default: + Logger.info.apply(this, args); + break; + } + } -} -const msgToClient: WsMessage[] = []; + function handleRuntimeError(clientMsg: WsMessage) { + const msg: WsMessage = { + category: 'buildUpdate', + type: 'completed', + data: { + diagnosticsHtml: generateRuntimeDiagnosticContent(config.rootDir, + config.buildDir, + clientMsg.data.message, + clientMsg.data.stack) + } + }; + queueMessageSend(msg); + } + +} export interface WsMessage { category: string; diff --git a/src/util/logger-diagnostics.ts b/src/util/logger-diagnostics.ts index f7e0a377..4b2eb998 100644 --- a/src/util/logger-diagnostics.ts +++ b/src/util/logger-diagnostics.ts @@ -1,8 +1,8 @@ import { BuildContext } from './interfaces'; import { Diagnostic, Logger, PrintLine } from './logger'; -import { titleCase } from './helpers'; -import { join } from 'path'; +import { join, resolve , normalize} from 'path'; import { readFileSync, unlinkSync, writeFileSync } from 'fs'; +import { splitLineBreaks, titleCase } from './helpers'; import * as chalk from 'chalk'; @@ -116,7 +116,7 @@ export function clearDiagnostics(context: BuildContext, type: string) { export function hasDiagnostics(buildDir: string) { - loadDiagnosticsHtml(buildDir); + loadBuildDiagnosticsHtml(buildDir); const keys = Object.keys(diagnosticsHtmlCache); for (var i = 0; i < keys.length; i++) { @@ -129,7 +129,7 @@ export function hasDiagnostics(buildDir: string) { } -function loadDiagnosticsHtml(buildDir: string) { +function loadBuildDiagnosticsHtml(buildDir: string) { try { if (diagnosticsHtmlCache[DiagnosticsType.TypeScript] === undefined) { diagnosticsHtmlCache[DiagnosticsType.TypeScript] = readFileSync(getDiagnosticsFileName(buildDir, DiagnosticsType.TypeScript), 'utf8'); @@ -155,35 +155,52 @@ export function injectDiagnosticsHtml(buildDir: string, content: any) { let contentStr = content.toString(); - const diagnosticsHtml: string[] = []; - diagnosticsHtml.push(`
`); - diagnosticsHtml.push(getDiagnosticsHtmlContent(buildDir)); - diagnosticsHtml.push(`
`); + const c: string[] = []; + c.push(`
`); + + // diagnostics content + c.push(getDiagnosticsHtmlContent(buildDir)); + + c.push(`
`); // #ion-diagnostics let match = contentStr.match(/(?![\s\S]*)/i); if (match) { - contentStr = contentStr.replace(match[0], match[0] + '\n' + diagnosticsHtml.join('\n')); + contentStr = contentStr.replace(match[0], match[0] + '\n' + c.join('\n')); } else { - contentStr = diagnosticsHtml.join('\n') + contentStr; + contentStr = c.join('\n') + contentStr; } return contentStr; } -export function getDiagnosticsHtmlContent(buildDir: string) { - loadDiagnosticsHtml(buildDir); +export function getDiagnosticsHtmlContent(buildDir: string, includeDiagnosticsHtml?: string) { + const c: string[] = []; + + // diagnostics header + c.push(` +
+
Error
+
+ +
+
+ `); + + if (includeDiagnosticsHtml) { + c.push(includeDiagnosticsHtml); + } - const diagnosticsHtml: string[] = []; + loadBuildDiagnosticsHtml(buildDir); const keys = Object.keys(diagnosticsHtmlCache); for (var i = 0; i < keys.length; i++) { if (typeof diagnosticsHtmlCache[keys[i]] === 'string') { - diagnosticsHtml.push(diagnosticsHtmlCache[keys[i]]); + c.push(diagnosticsHtmlCache[keys[i]]); } } - return diagnosticsHtml.join('\n'); + return c.join('\n'); } @@ -194,13 +211,24 @@ function generateDiagnosticHtml(d: Diagnostic) { c.push(`
`); - const header = `${titleCase(d.type)} ${titleCase(d.level)}`; - c.push(`
${escapeHtml(header)}
`); + const title = `${titleCase(d.type)} ${titleCase(d.level)}`; + c.push(`
${escapeHtml(title)}
`); c.push(`
${escapeHtml(d.messageText)}
`); c.push(`
`); // .ion-diagnostic-masthead + c.push(generateCodeBlock(d)); + + c.push(``); // .ion-diagnostic + + return c.join('\n'); +} + + +function generateCodeBlock(d: Diagnostic) { + const c: string[] = []; + c.push(`
`); c.push(`
${escapeHtml(d.relFileName)}
`); @@ -238,12 +266,114 @@ function generateDiagnosticHtml(d: Diagnostic) { c.push(`
`); // .ion-diagnostic-file - c.push(``); // .ion-diagnostic - return c.join('\n'); } +export function generateRuntimeDiagnosticContent(rootDir: string, buildDir: string, runtimeErrorMessage: string, runtimeErrorStack: string) { + let c: string[] = []; + + c.push('
'); + c.push('
'); + c.push('
Runtime Error
'); + if (runtimeErrorMessage) { + c.push('
' + escapeHtml(runtimeErrorMessage) + '
'); + } + c.push('
'); // .ion-diagnostic-masthead + + const diagnosticsHtmlCache = generateRuntimeStackDiagnostics(rootDir, runtimeErrorStack); + diagnosticsHtmlCache.forEach(d => { + c.push(generateCodeBlock(d)); + }); + + if (runtimeErrorStack) { + c.push('
Stack
'); + c.push('
' + escapeHtml(runtimeErrorStack) + '
'); + } + + c.push('
'); // .ion-diagnostic + + return getDiagnosticsHtmlContent(buildDir, c.join('\n')); +} + + +export function generateRuntimeStackDiagnostics(rootDir: string, stack: string) { + const diagnostics: Diagnostic[] = []; + + if (stack) { + splitLineBreaks(stack).forEach(stackLine => { + try { + const match = WEBPACK_FILE_REGEX.exec(stackLine); + if (!match) return; + + const fileSplit = match[1].split('?'); + if (fileSplit.length !== 2) return; + + const linesSplit = fileSplit[1].split(':'); + if (linesSplit.length !== 3) return; + + const fileName = fileSplit[0]; + const errorLineIndex = parseInt(linesSplit[1], 10); + const errorCharIndex = parseInt(linesSplit[2], 10); + + const d: Diagnostic = { + level: 'error', + syntax: 'js', + type: 'runtime', + header: '', + code: 'runtime', + messageText: '', + absFileName: resolve(rootDir, fileName), + relFileName: normalize(fileName), + lines: [] + }; + + const srcLines = splitLineBreaks(readFileSync(d.absFileName, 'utf8')); + if (!srcLines.length || errorLineIndex >= srcLines.length) return; + + const errorLine: PrintLine = { + lineIndex: errorLineIndex, + lineNumber: errorLineIndex + 1, + text: srcLines[errorLineIndex], + errorCharStart: errorCharIndex, + errorLength: 1 + }; + d.lines.push(errorLine); + + if (errorLine.lineIndex > 0) { + const beforeLine: PrintLine = { + lineIndex: errorLine.lineIndex - 1, + lineNumber: errorLine.lineNumber - 1, + text: srcLines[errorLine.lineIndex - 1], + errorCharStart: -1, + errorLength: -1 + }; + d.lines.unshift(beforeLine); + } + + if (errorLine.lineIndex < srcLines.length) { + const beforeLine: PrintLine = { + lineIndex: errorLine.lineIndex + 1, + lineNumber: errorLine.lineNumber + 1, + text: srcLines[errorLine.lineIndex + 1], + errorCharStart: -1, + errorLength: -1 + }; + d.lines.push(beforeLine); + } + + diagnostics.push(d); + + } catch (e) {} + }); + } + + return diagnostics; +}; + +const WEBPACK_FILE_REGEX = /\(webpack:\/\/\/(.*?)\)/; + + function htmlHighlightError(errorLine: string, errorCharStart: number, errorLength: number) { const lineChars: string[] = []; const lineLength = Math.max(errorLine.length, errorCharStart + errorLength); @@ -301,6 +431,7 @@ function cssConsoleSyntaxHighlight(text: string, errorCharStart: number) { return chars.join(''); } + function escapeHtml(unsafe: string) { return unsafe .replace(/&/g, '&') @@ -308,8 +439,7 @@ function escapeHtml(unsafe: string) { .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); - } - +} function removeWhitespaceIndent(orgLines: PrintLine[]) {