Initial commit

This commit is contained in:
Linus Miller 2019-02-13 21:26:28 +01:00
commit 3cd6435536
47 changed files with 15930 additions and 0 deletions

9
.babelrc Normal file
View File

@ -0,0 +1,9 @@
{
"presets": [[
"@babel/preset-env", {
"modules": false,
"shippedProposals": true
}
]],
"plugins": ["@babel/transform-react-jsx"]
}

12
.eslintrc Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "standard",
"rules": {
"comma-dangle": [ 2, "always-multiline" ],
"no-unused-vars": [ 2, "all" ]
},
"globals": {
"fetch": true
}
}

66
.gitignore vendored Normal file
View File

@ -0,0 +1,66 @@
# from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
/dump
/public

9
browserslist Normal file
View File

@ -0,0 +1,9 @@
# Define what browsers we support
# https://github.com/ai/browserslist
#
# This is automatically sourced by babel-preset-env and autoprefixer (postcss)
> 10%
last 2 versions
iOS >= 9
ie >= 11

149
errors.txt Normal file
View File

@ -0,0 +1,149 @@
GhostingService.js?a92c:221 Uncaught TypeError: Cannot read property 'y' of null
at isPlacedInPropping (GhostingService.js?a92c:221)
at Array.filter (<anonymous>)
at toggleProppingOnCollidingElement (GhostingService.js?a92c:220)
at Object.removeGhost (GhostingService.js?a92c:57)
at Object.out (DropAreas.js?510e:307)
at eval (DnDLib.js?b5c7:55)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at fireDropEvents (interact.js?f3bb:1833)
margin
border
padding
2533 × 1941.970
Console
top
All levels
Group similar

angular.js:18104 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$apply (angular.js:18104)
at Scope.$delegate.$safeApply (safeApply.js?2440:19)
at Object.dragEnd [as end] (MoveUnits.js?949c:183)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
interact.js?f3bb:1177 [Violation] 'pointerup' handler took 617ms
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$emit (angular.js:18249)
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
jquery.js:4544 [Violation] 'mouseup' handler took 392ms
angular.js:14199 Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.5.11/$rootScope/inprog?p0=NaNigest
at https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:68:12
at beginPhase (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18357:15)
at Scope.$apply (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18092:11)
at HTMLDivElement.<anonymous> (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:26597:23)
at HTMLDivElement.dispatch (https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js:4737:27)
at HTMLDivElement.elemData.handle (https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js:4549:28)
angular.js:14199 Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.5.11/$rootScope/inprog?p0=NaNigest
at https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:68:12
at beginPhase (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18357:15)
at Scope.$digest (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:17785:9)
at Scope.$apply (https://cdnjs.cloudflare.com/ajax/libs/a
12
[Violation] 'pointermove' handler took <N>ms
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$emit (angular.js:18249)
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
6
[Violation] 'pointerup' handler took <N>ms
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$emit (angular.js:18249)
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
6
[Violation] 'mouseup' handler took <N>ms
[Violation] Forced reflow while executing JavaScript took 34ms
jquery.js:5487 Uncaught RangeError: Maximum call stack size exceeded
at HTMLLIElement.<anonymous> (jquery.js:5487)
at jquery.js:142
at Function.map (jquery.js:452)
at jQuery.fn.init.map (jquery.js:141)
at jQuery.fn.init.clone (jquery.js:5486)
at Object.removeGhost (GhostingService.js?882c:52)
at Object.out (DropAreas.js?510e:307)
at eval (DnDLib.js?f6dd:50)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$emit (angular.js:18249)
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
angular.js:18104 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$apply (angular.js:18104)
at Scope.$delegate.$safeApply (safeApply.js?2440:19)
at Object.dragEnd [as end] (MoveUnits.js?949c:183)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
at Scope.$emit (angular.js:18249)
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
at eval (DnDLib.js?f6dd:104)
at fireUntilImmediateStopped (interact.js?f3bb:47)
at Eventable.fire (interact.js?f3bb:65)
at Interactable.fire (interact.js?f3bb:517)
at firePrepared (interact.js?f3bb:1376)
at eval (interact.js?f3bb:1368)
at Signals.fire (interact.js?f3bb:5768)

3
nodemon.json Normal file
View File

@ -0,0 +1,3 @@
{
"ext": "js,ejs"
}

14270
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
package.json Normal file
View File

@ -0,0 +1,55 @@
{
"name": "stack-trace-mapper",
"version": "0.0.1",
"description": "",
"main": "server/server.js",
"scripts": {
"lint": "eslint .",
"test": "echo \"Error: no test specified\" && exit 1",
"webpack": "webpack"
},
"author": "Linus Miller <lohfu@lohfu.io> (https://lohfu.io/)",
"license": "MIT",
"dependencies": {
"@bmp/console": "0.0.5",
"@bmp/highlight-stack": "0.0.2",
"body-parser": "^1.18.3",
"classnames": "^2.2.5",
"ejs": "^2.6.1",
"express": "^4.16.3",
"lowline": "^0.2.2",
"morgan": "^1.9.0",
"multer": "^1.3.0",
"pg": "^7.4.3",
"react": "^16.4.0",
"react-dom": "^16.4.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"source-map": "^0.7.3"
},
"devDependencies": {
"@babel/cli": "^7.0.0-beta.49",
"@babel/core": "^7.0.0-beta.49",
"@babel/plugin-transform-react-jsx": "^7.0.0-beta.49",
"@babel/preset-env": "^7.0.0-beta.49",
"autoprefixer": "^8.5.1",
"babel-loader": "^8.0.0-beta.2",
"css-loader": "^0.28.11",
"eslint": "^4.19.1",
"eslint-config-standard": "^11.0.0",
"eslint-config-standard-react": "^6.0.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-standard": "^3.1.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"postcss-color-function": "^4.0.1",
"postcss-loader": "^2.1.5",
"precss": "^3.1.2",
"style-loader": "^0.21.0",
"webpack": "^4.9.1",
"webpack-cli": "^2.1.4",
"webpack-dev-middleware": "^3.1.3"
}
}

25
postcss.config.js Normal file
View File

@ -0,0 +1,25 @@
'use strict'
const env = process.env.NODE_ENV || 'development'
const config = {
development: {
plugins: {
precss: {},
autoprefixer: {},
'postcss-color-function': {},
},
},
production: {
plugins: {
precss: {},
autoprefixer: {},
cssnano: {
preset: 'default',
},
},
},
}
module.exports = config[env]

13
server/config/postgres.js Normal file
View File

@ -0,0 +1,13 @@
'use strict'
module.exports = {
database: 'stack_trace_mapper', // process.env.NODE_ENV var: PGDATABASE
user: 'stack_trace_mapper',
password: 'thesesourcemapsaregoingtochangetheworld',
// host: 'hq.bitmill.co', // Server hosting the postgres database
host: 'localhost', // Server hosting the postgres database
// port: 6543, // process.env.NODE_ENV var: PGPORT
port: 5432, // process.env.NODE_ENV var: PGPORT
max: 10, // max number of clients in the pool
idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed
}

12
server/db.js Normal file
View File

@ -0,0 +1,12 @@
'use strict'
const { Pool } = require('pg')
const config = require('./config/postgres')
const pool = new Pool(config)
module.exports = {
query: (text, params, callback) => {
return pool.query(text, params, callback)
},
}

View File

@ -0,0 +1,41 @@
'use strict'
const db = require('../db')
function create (sourceMap) {
return db.query(
'INSERT INTO source_maps(application, version, ui_platform, content) VALUES($1, $2, $3, $4) RETURNING *;',
[ sourceMap.application, sourceMap.version, sourceMap.uiPlatform, sourceMap.content ]
).then((result) => result.rows[0])
}
function listAll () {
return db
.query('SELECT id, application, version, ui_platform as "uiPlatform", created_at as "createdAt" FROM source_maps;')
.then((result) => result.rows)
}
function getAll () {
return db.query('SELECT * FROM source_maps').then((result) => result.rows)
}
function get (sourceMap) {
const values = [ sourceMap.application, sourceMap.version, sourceMap.uiPlatform ]
return db
.query('SELECT * FROM source_maps WHERE application = $1 AND version = $2 AND ui_platform = $3', values)
.then((result) => {
if (result.rowCount <= 0) {
throw new Error('No source map found')
}
return result.rows[0]
})
}
module.exports = {
create,
get,
getAll,
listAll,
}

View File

@ -0,0 +1,9 @@
'use strict'
const multer = require('multer')
const memoryStorage = multer.memoryStorage()
const upload = multer({ storage: memoryStorage })
module.exports = upload

View File

@ -0,0 +1,62 @@
'use strict'
const _ = require('lodash')
const remapper = require('../util/remap')
const handlers = require('../handlers/source-maps')
const { SourceMapConsumer } = require('source-map')
function create (req, res) {
const sourceMap = _.mapKeys(req.body, (val, key) => _.camelCase(key))
if (!sourceMap.content && req.file) {
sourceMap.content = req.file.buffer.toString('utf8')
}
console.log(sourceMap)
handlers.create(sourceMap).then(() => {
res.render('index')
})
}
function getOne (req, res, next) {
next()
}
function listAll (req, res, next) {
handlers.listAll().then((sourceMaps) => {
res.locals.sourceMaps = sourceMaps
res.json(sourceMaps)
}).catch(next)
}
function remap (req, res, next) {
const body = _.mapKeys(req.body, (val, key) => _.camelCase(key))
const promise = body.content ? Promise.resolve(body) : handlers.get(body)
promise
.then((sourceMap) => new SourceMapConsumer(sourceMap.content))
.then((consumer) => {
const remapped = remapper(body.stack, consumer)
consumer.destroy()
if (req.accepts([ 'json', 'html' ]) === 'json') {
res.json(remapped)
} else {
res.locals.stack = remapped
res.render('remap-result')
}
})
.catch(next)
}
module.exports = {
remap,
create,
getOne,
listAll,
}

24
server/routes.js Normal file
View File

@ -0,0 +1,24 @@
'use strict'
const upload = require('./middleware/multer')
const sourceMaps = require('./middleware/source-maps')
module.exports = (server) => {
server.get('/:page?', (req, res) => {
res.render('index')
})
// server.get('/upload', sourceMaps.listAll, (req, res) => {
// res.render('upload')
// })
// server.get('/remap', sourceMaps.listAll, (req, res) => {
// res.render('remap')
// })
server.get('/api/source-maps', sourceMaps.listAll)
server.post('/api/source-maps', upload.single('map'), sourceMaps.create)
server.get('/api/source-maps/:id', sourceMaps.getOne)
server.post('/api/remap', sourceMaps.remap)
}

61
server/server.js Normal file
View File

@ -0,0 +1,61 @@
'use strict'
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
// modules > native
const path = require('path')
// modules > 3rd party
const express = require('express')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const highlightStack = require('@bmp/highlight-stack')
process.on('uncaughtException', (err) => {
console.error(chalk.red('UNCAUGHT EXCEPTION'))
if (err.stack) {
console.error(highlightStack(err.stack))
} else {
console.error(err)
}
process.exit(1)
})
const server = express()
// set template engine
server.set('view engine', 'ejs')
server.set('views', path.join(__dirname, 'templates'))
server.use('/', express.static('public'))
// set up console logs in dev mode
if (process.env.NODE_ENV !== 'production') {
require('@bmp/console')({ log: true, error: true, dir: true })
const morgan = require('morgan')
server.use(morgan('dev'))
const webpackMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')
const webpackConfig = require('../webpack.config')
server.use(webpackMiddleware(webpack(webpackConfig)))
}
server.use(bodyParser.json())
server.use(bodyParser.urlencoded({ extended: true }))
// server.use('/', (req, res, next) => {
// console.log('hello')
// res.send('<h1>Hello</h1>')
// })
const port = process.env.port || 1337
console.log(path.resolve(__dirname, '../'))
require('./routes')(server)
server.listen(port, () => {
console.info(`[${chalk.cyan('INIT')}] HTTP Server listening on port ${chalk.magenta(port)} (${chalk.yellow(server.get('env'))})`)
})

View File

@ -0,0 +1,4 @@
</div>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Cybercom Sourcemap Magic</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/main.css" />
<!-- <script src="/js/app.mjs" type="module"></script> -->
<!-- <script src="/bundle.js" type="module"></script> -->
</head>
<body>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Cybercom Stack Trace Mapper</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bundle.css" />
</head>
<body>
<div id='app'></div>
<script src='/js/bundle.js'></script>
</body>
</html>

View File

@ -0,0 +1,5 @@
<h1>Remapped</h1>
<pre class='mapped-stack'>
<%- stack.join('\n') %>
</pre>

View File

@ -0,0 +1,35 @@
<% include header %>
<div class="remap page">
<h1>Remap Stack Trace</h1>
<h2>Original</h2>
<form action="/api/remap" method="POST">
<div class="row">
<label>Application</label>
<input type="text" name="application" placeholder="Application" value="algot" />
</div>
<div class="row">
<label>UI Platform</label>
<input type="text" name="ui_platform" placeholder="UI Platform" value="IRW" />
</div class="row">
<div class="row">
<label>Version</label>
<input type="text" name="version" placeholder="Version" value="4.3.0-alpha.0" />
</div>
<div class="row">
<label>Stack</label>
<textarea name="stack"></textarea>
</div>
<div>
<button type="submit">Apply</button>
</div>
</form>
<pre class='original-stack'></pre>
<h2>Remapped</h2>
<pre class='remapped-stack'></pre>
</div>

View File

@ -0,0 +1,40 @@
<% include header %>
<h2>Upload a source map</h2>
<div class="upload page">
<form action='/api/upload' method='POST' enctype='multipart/form-data'>
<div>
<label for='application'>Application</label>
<input type='text' name='application' placeholder='Application' value='algot' />
</div>
<div>
<label for='application'>UI</label>
<input type='text' name='uiPlatform' placeholder='UI' />
</div>
<div>
<label for='application'>Version</label>
<input type='text' name='version' placeholder='Version' value='4.3.0' />
</div>
<div>
<input type='file' name='map' required />
</div>
<div>
<button type='submit'>Upload</button>
</div>
</form>
<div class="source-map-list">
<ul>
<% for (const sourceMap of sourceMaps) { %>
<li><%- sourceMap.application %> <%- sourceMap.version %> <%- sourceMap.uiPlatform %></li>
<% } %>
</ul>
</div>
</div>
<script src="/js/upload.mjs"></script>
<% include footer %>

62
server/test-convert.js Normal file
View File

@ -0,0 +1,62 @@
'use strict'
const fs = require('fs')
const path = require('path')
// const sourceMap = require('source-map');
const { SourceMapConsumer } = require('source-map')
const convert = require('./convert')
// const map = fs.readFileSync(path.join(__dirname, 'algot-irw/isa.js.map'), 'utf8');
const map = fs.readFileSync(path.join(__dirname, 'algot-irw/isa.js.map'), 'utf8')
// console.log(map);
const str = `
Error
at Object._ [as removeGhost] (isa.js:120)
at Object.i [as out] (isa.js:120)
at isa.js:120
at r (isa.js:24)
at e.fire (isa.js:24)
at e.fire (isa.js:24)
at l (isa.js:24)
at isa.js:24
at e.fire (isa.js:24)
at e.doMove (isa.js:24)
`
const chrome = `Error: Testing error
at Object.y [as addGhost] (http://localhost:8080/algot/irw/js/isa.js:120:377375)
at Object.n [as over] (http://localhost:8080/algot/irw/js/isa.js:120:263605)
at http://localhost:8080/algot/irw/js/isa.js:120:373131
at r (http://localhost:8080/algot/irw/js/isa.js:24:111023)
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:111215)
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:116563)
at l (http://localhost:8080/algot/irw/js/isa.js:24:131058)
at http://localhost:8080/algot/irw/js/isa.js:24:132002
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:178230)
at e.doMove (http://localhost:8080/algot/irw/js/isa.js:24:123026)
`
const firefox = `
y@http://localhost:8080/algot/irw/js/isa.js:120:377375
n@http://localhost:8080/algot/irw/js/isa.js:120:263603
r/<@http://localhost:8080/algot/irw/js/isa.js:120:373129
r@http://localhost:8080/algot/irw/js/isa.js:24:111023
[2]</a</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:111215
[4]</M</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:116551
l@http://localhost:8080/algot/irw/js/isa.js:24:131056
[8]</<@http://localhost:8080/algot/irw/js/isa.js:24:132002
[34]</r</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:178230
[5]</v</e.prototype.doMove@http://localhost:8080/algot/irw/js/isa.js:24:123024
[5]</v</e.prototype.pointerMove@http://localhost:8080/algot/irw/js/isa.js:24:122745
r/<@http://localhost:8080/algot/irw/js/isa.js:24:120083`
// const chrome = 'Error: Testing error at Object.y [as addGhost] (http://localhost:8080/algot/irw/js/isa.js:120:377375) at Object.n [as over] (http://localhost:8080/algot/irw/js/isa.js:120:263605) at http://localhost:8080/algot/irw/js/isa.js:120:373131 at r (http://localhost:8080/algot/irw/js/isa.js:24:111023) at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:111215) at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:116563) at l (http://localhost:8080/algot/irw/js/isa.js:24:131058) at http://localhost:8080/algot/irw/js/isa.js:24:132002 at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:178230) at e.doMove (http://localhost:8080/algot/irw/js/isa.js:24:123026)'
SourceMapConsumer.with(map, null, (consumer) => {
console.log(convert(chrome, consumer))
console.log('\n\n\n')
console.log(convert(firefox, consumer))
})

84
server/util/remap.js Normal file
View File

@ -0,0 +1,84 @@
'use strict'
const { SourceMapConsumer } = require('source-map')
module.exports = function stackTraceMapper (stack, map) {
if (!(map instanceof SourceMapConsumer)) {
throw new Error('map is not a consumer')
}
const setups = [
{
// settings for Chrome, Edge and IE (Plus?)
regex: /\s+at(?:\s+([^\s]+))?\s\(?([/a-z0-9:.-]+):([0-9]+):([0-9]+)/,
// regex: /\s+at(?:\s+([^)]+))?\s\(?([/a-z0-9:.-]+):([0-9]+):([0-9]+)/,
skip: 1,
split: /\n?(?= at )/,
},
{
// settings for Firefox and Safari
regex: /([a-zA-Z0-9]*)@(.*):([0-9]+):([0-9]+)/,
skip: 0,
split: /\s+/,
},
]
const setup = setups.find((setup) => {
return setup.regex.test(stack)
})
if (!setup) {
throw new Error('Stack trace is not recognised')
}
const split = stack.trim().split(setup.split)
// TODO whether the error name and similar preludes the stack varies between browsers perhaps we should create one
const pre = split.slice(0, setup.skip)
console.log('split', split)
console.log('pre', pre)
const lines = split.slice(setup.skip).map((source) => {
// TODO handle lines that have not matched
const match = source.match(setup.regex)
if (!match) {
return { source }
}
const [ , fnc, file, line, column ] = match
return { fnc, file, line: parseInt(line), column: parseInt(column), source }
})
const originalLines = lines.map((obj) => {
if (obj.line) {
return map.originalPositionFor({ line: obj.line, column: obj.column })
}
return obj
})
// need to do research as to why, but the original line objects returned
// contain the function call names are shifted one line compared with
// the sourcemapped stack printed in chrome dev tools
const adjustedLines = originalLines.map((obj, index, arr) => {
if (obj.line) {
const prev = arr[index + 1]
obj.name = (prev && prev.name) || undefined
}
return obj
})
return pre.concat(adjustedLines.map(formatLine))
}
function formatLine ({ source, line, column, name = '(unknown)' }) {
if (!line || !column) {
return ` ${source}`
}
return ` at ${name} (${source}:${line}:${column})`
}

12
src/.eslintrc Normal file
View File

@ -0,0 +1,12 @@
{
"extends": [
"../../.eslintrc",
"standard-react"
],
"globals": {
"fetch": true,
"google": true,
"INITIAL_CONTEXT": true
}
}

39
src/components/Layout.jsx Normal file
View File

@ -0,0 +1,39 @@
import React from 'react'
import PropTypes from 'prop-types'
import { get } from 'lowline'
import Navigation from './Navigation'
class Layout extends React.Component {
componentDidUpdate () {
console.log('this did update')
const { location } = this.props
const offset = get(window.history, 'state.scrollTop') || 0
window.scrollTo(0, offset)
}
render () {
// const { page, navigation, location, organization, children } = this.props
const { routes, children } = this.props
return (
<div className='container'>
<header>
<h1><a href='/'>Stack Trace Mapper</a></h1>
<Navigation routes={routes} />
</header>
{children}
</div>
)
}
}
Layout.propTypes = {
routes: PropTypes.array,
}
export default Layout

View File

@ -0,0 +1,88 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { bindAll } from 'lowline'
import NavigationItem from './NavigationItem'
class Navigation extends React.Component {
constructor (options = {}) {
super(options)
this.state = { visible: false }
bindAll(this, ['hide', 'toggle'])
}
// componentWillReceiveProps ({ location }) {
// if (this.props.location.pathname !== location.pathname && this.state.visible) {
// this.setState({
// visible: false,
// })
// }
// }
// shouldComponentUpdate ({ location }, nextState) {
// return nextState.visible !== this.state.visible || this.props.location.pathname !== location.pathname
// }
// componentDidUpdate ({ location }) {
// if (this.props.location !== location) {
// }
// }
toggle () {
if (!this.state.visible) {
this.show()
} else {
this.hide()
}
}
show () {
if (!this.state.visible) {
document.body.classList.add('shift')
this.setState({
visible: true,
})
}
}
hide () {
if (this.state.visible) {
document.body.classList.remove('shift')
this.setState({
visible: false,
})
}
}
render () {
const { routes, location } = this.props
const { visible } = this.state
return (
<nav className={classNames({visible})}>
<button onClick={this.toggle}>
<div className='bar' />
<div className='bar' />
<div className='bar' />
</button>
<ul onClick={this.hide}>
{routes && routes.map((route) => <NavigationItem key={route.name} location={location} {...route} />)}
</ul>
{this.state.visible && <div onClick={this.hide} className='cover' />}
</nav>
)
}
}
Navigation.propTypes = {
routes: PropTypes.array,
}
export default Navigation

View File

@ -0,0 +1,12 @@
import React from 'react'
import classNames from 'classnames'
import { Link } from 'react-router-dom'
const NavigationItem = ({ location, name, title, path, routes }) => (
<li className={classNames({ [name]: true, current: location && (location.pathname === path || (path !== '/' && location.pathname.startsWith(path))) })}>
<Link to={path}><span>{title}</span></Link>
{routes && <ul>{routes.map((route) => route.nav !== false && <NavigationItem key={route.name} location={location} path={path + route.path} {...route} />)}</ul>}
</li>
)
export default NavigationItem

10
src/components/Remap.jsx Normal file
View File

@ -0,0 +1,10 @@
import React from 'react'
import RemapForm from './RemapForm'
export default () => (
<div className='remap page'>
<h1>Remap Stack Trace</h1>
<RemapForm />
</div>
)

View File

@ -0,0 +1,159 @@
import React from 'react'
import PropTypes from 'prop-types'
import { bindAll } from 'lowline'
const split = /\n?(?= at )/
const preference = [ 'irw', 'mobile', 'nwp', 'kiosk', 'm2' ]
export default class RemapForm extends React.Component {
constructor () {
super()
this.form = React.createRef()
bindAll(this, [ 'onSubmit', 'selectApplication', 'selectUiPlatform' ])
this.state = {
error: false,
success: false,
}
}
componentDidMount () {
fetch('/api/source-maps')
.then((res) => res.json())
.then((sourceMaps) => {
this.setState({ sourceMaps })
this.applications = {};
sourceMaps.forEach((sourceMap) => {
if (!this.applications[sourceMap.application]) {
this.applications[sourceMap.application] = {};
}
if (!this.applications[sourceMap.application][sourceMap.uiPlatform]) {
this.applications[sourceMap.application][sourceMap.uiPlatform] = [];
}
this.applications[sourceMap.application][sourceMap.uiPlatform].push(sourceMap.version)
})
const applications = Object.keys(this.applications);
this.setState({
applications,
})
this.selectApplication(applications[0])
})
}
onSubmit (e) {
e.preventDefault()
const data = {}
for (let el of this.form.current.elements) {
if (el.name) {
data[el.name] = el.value
}
}
fetch('/api/remap', {
method: 'POST',
headers: {
accepts: 'application/json',
'content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((res) => {
this.setState({
success: true,
error: false,
original: data.stack.split(split).join('\n'),
mapped: res.join('\n'),
})
})
.catch((res) => {
this.setState({
success: false,
error: true,
})
})
}
selectApplication (value) {
if (value.target && value.target.value) {
value = value.target.value
}
// const uiPlatforms = Object.keys(this.applications[value])
const application = this.applications[value]
this.setState({
application,
// uiPlatforms,
})
this.selectUiPlatform(Object.keys(application)[0])
}
selectUiPlatform (value) {
if (value.target && value.target.value) {
value = value.target.value
}
console.log(this.state.application)
this.setState({
versions: this.state.application[value]
})
}
render () {
const { applications, application, uiPlatform, uiPlatforms, versions, error, success, original, mapped } = this.state
return (
<div className="remap-form">
<form ref={this.form} action='/api/remap' method='POST' onSubmit={this.onSubmit}>
<div className='row'>
<label>Application</label>
<select name='application' onChange={this.selectApplication}>
{applications && applications.map((application) => <option key={application} value={application}>{application}</option>)}
</select>
</div>
<div className='row'>
<label>UI Platform</label>
<select name='ui_platform' onChange={this.selectUiPlatform}>
{application && Object.keys(application).map((ui) => <option key={ui} value={ui}>{ui}</option>)}
</select>
</div>
<div className='row'>
<label>Version</label>
<select name='version'>
{versions && versions.map((version) => <option key={version} value={version}>{version}</option>)}
</select>
</div>
<div className='row'>
<label>Stack</label>
<textarea name='stack' />
</div>
<div>
<button type='submit'>Apply</button>
</div>
</form>
<div className='output'>
{original && <div class='output-original'><h2>Original</h2><pre>{original}</pre></div>}
{mapped && <div class='output-mapped'><h2>Mapped</h2><pre>{mapped}</pre></div>}
</div>
</div>
)
}
}

9
src/components/Start.jsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react'
export default () => (
<div className='start'>
<h1>Welcome</h1>
<p>To the most legit website in the world. Besides mapping stack traces you can also buy totally ok drugs.</p>
</div>
)

26
src/components/Upload.jsx Normal file
View File

@ -0,0 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import UploadForm from './UploadForm'
const Upload = ({ sourceMaps }) => (
<div className='upload page'>
<h2>Upload a source map</h2>
<div className='content'>
<UploadForm />
<div className='source-map-list'>
<ul>
{sourceMaps && sourceMaps.map((sourceMap) => <li>{sourceMap.application} {sourceMap.version} {sourceMap.uiPlatform}</li>)}
</ul>
</div>
</div>
</div>
)
Upload.propTypes = {
sourceMaps: PropTypes.array,
}
export default Upload

View File

@ -0,0 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
export default class UploadForm extends React.Component {
constructor () {
super()
this.form = React.createRef()
this.onSubmit = this.onSubmit.bind(this)
this.state = {
error: false,
success: false,
}
}
onSubmit (e) {
e.preventDefault()
const data = new FormData(this.form.current)
fetch('/api/source-maps', {
method: 'POST',
headers: {
accepts: 'application/json',
},
body: data,
})
.then((res) => res.json())
.then((res) => {
this.setState({
success: true,
error: false,
})
})
.catch((res) => {
this.setState({
success: false,
error: true,
})
})
}
render () {
// const { error, success } = this.state
const error = true
const success = true
return (
<form ref={this.form} action='/api/source-maps' method='POST' encType='multipart/form-data' onSubmit={this.onSubmit}>
{success && <div className='success message'>Source Map Uploaded</div>}
{error && <div className='error message'>Error Uploading Source Map</div>}
<div>
<label htmlFor='application'>Application</label>
<input type='text' name='application' placeholder='Application' defaultValue='algot' />
</div>
<div>
<label htmlFor='application'>UI</label>
<input type='text' name='uiPlatform' placeholder='UI' />
</div>
<div>
<label htmlFor='application'>Version</label>
<input type='text' name='version' placeholder='Version' defaultValue='4.3.0' />
</div>
<div>
<input type='file' name='map' required />
</div>
<div>
<button type='submit'>Upload</button>
</div>
</form>
)
}
}

35
src/index.jsx Normal file
View File

@ -0,0 +1,35 @@
import './styles/main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Layout from './components/Layout'
import routes from './routes'
import { store } from './util/history'
ReactDOM.render((
<Router>
<Layout routes={routes}>
{routes.map((route, i) => <Route key={i} exact path={route.path} component={route.component} />)}
</Layout>
</Router>
), document.getElementById('app'))
let timeout
function remember (attrs) {
console.log('should be storing')
store({
scrollTop: window.pageYOffset,
})
}
window.addEventListener('scroll', () => {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(remember, 200)
}, { passive: false })

6
src/module.mjs Normal file
View File

@ -0,0 +1,6 @@
console.log('what up again. this time some thing up.')
export const aPoo = 'smelly'
export const aSmellierPoo = 'super smelly'
export default 'you just missed a smelly poo'

34
src/remap.mjs Normal file
View File

@ -0,0 +1,34 @@
import mod from './module'
const form = document.querySelector('form')
const pre = document.querySelector('.remapped-stack')
console.log(mod)
form.addEventListener('submit', (e) => {
e.preventDefault()
const obj = {}
for (let el of e.target.elements) {
if (el.name) {
obj[el.name] = el.value
}
}
console.log(obj)
fetch('/api/remap', {
method: 'POST',
headers: {
accepts: 'application/json',
'content-type': 'application/json',
},
body: JSON.stringify(obj),
})
.then((res) => res.json())
.then((res) => {
pre.textContent = res.join('\n')
})
})

20
src/routes.js Normal file
View File

@ -0,0 +1,20 @@
import Start from './components/Start'
import Upload from './components/Upload'
import Remap from './components/Remap'
export default [ {
path: '/',
component: Start,
name: 'start',
title: 'Start',
}, {
path: '/upload',
component: Upload,
name: 'upload',
title: 'Upload',
}, {
path: '/remap',
component: Remap,
name: 'remap',
title: 'Remap',
} ]

View File

@ -0,0 +1,30 @@
.remap.page {
.remap-form {
display: flex;
> form {
flex: 0 0 500px;
max-width: 500px;
margin-right: $global-gutter;
}
> div {
flex: 1 1 auto;
}
}
form {
> div {
margin: 4px;
> label {
max-width: 120px;
}
}
textarea {
min-height: 340px;
}
}
}

View File

66
src/styles/layout.css Normal file
View File

@ -0,0 +1,66 @@
@import './mixins';
#app {
> .container {
@include .container($content-max-width, $global-gutter);
display: flex;
flex-direction: column;
> header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
> h1 {
> a {
color: black;
}
}
}
}
}
nav {
> button {
display: none;
}
> ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
> li {
display: block;
margin: 5px;
&:first-child {
margin-left: 0px;
}
&:last-child {
margin-right: 0px;
}
> a {
cursor: pointer;
display: block;
font-weight: bold;
border: 5px solid black;
padding: $button-padding;
color: black;
text-decoration: none;
transition: all 0.5s;
&:hover {
background: black;
color:white;
}
}
}
}
}

5
src/styles/main.css Normal file
View File

@ -0,0 +1,5 @@
@import './variables';
@import './layout';
@import './styles';
@import './content/upload';
@import './content/remap';

57
src/styles/mixins.css Normal file
View File

@ -0,0 +1,57 @@
$content-max-width: 1000px !default;
$global-gutter: 30px !default;
@mixin .container($max-width: $content-max-width, $gutter: $global-gutter) {
width: calc(100% - $gutter);
max-width: $max-width;
margin: 0 auto;
}
@mixin .grid($direction: row) {
display: flex;
flex-wrap: wrap;
flex-direction: $direction;
justify-content: flex-start;
}
@mixin .cell($count: 5, $gutter: $global-gutter) {
flex: 0 0 auto;
width: calc(100% / $count ~"-" (($count - 1) * $gutter / $count));
margin-right: $gutter;
&:nth-child($(count)n) {
margin-right: 0;
margin-left: auto;
}
}
@mixin .wipe {
/*Reset's every elements apperance*/
background: none repeat scroll 0 0 transparent;
border: medium none;
border-spacing: 0;
border-radius: 0;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
margin: 0;
padding: 0;
text-align: left;
text-decoration: none;
text-transform: inherit;
text-indent: 0;
}
@mixin .wipe-button {
@include .wipe;
&:focus {
outline: 0;
}
&::-moz-focus-inner {
border: 0;
padding: 0;
}
}

59
src/styles/styles.css Normal file
View File

@ -0,0 +1,59 @@
@import './mixins';
*, *:before, *:after {
box-sizing: border-box;
}
a {
text-decoration: none;
color: #ffcc33;
}
.grid {
@include .grid();
}
button {
@include .wipe-button;
cursor: pointer;
display: block;
border: 5px solid black;
padding: $button-padding;
color: white;
background: black;
font-weight: bold;
transition: all 0.5s;
&:hover {
background: white;
color: black;
}
}
input, textarea {
margin: 0;
padding: 8px;
width: 100%;
}
.message {
border-left: 5px solid transparent;
padding: 15px 20px;
&.error {
$color: red;
color: color($color shade(20%));
border-left-color: color($color shade(20%));
background: color($color tint(90%));
}
&.success {
$color: green;
color: color($color shade(20%));
border-left-color: color($color shade(20%));
background: color($color tint(90%));
}
}

4
src/styles/variables.css Normal file
View File

@ -0,0 +1,4 @@
$content-max-width: 1280px;
$global-gutter: 30px;
$button-padding: 10px 20px;

30
src/upload.mjs Normal file
View File

@ -0,0 +1,30 @@
const form = document.querySelector('form')
const pre = document.querySelector('.remapped-stack')
form.addEventListener('submit', (e) => {
e.preventDefault()
console.log(e.target)
const data = new FormData(form)
console.log('should be appending')
data.append('testing', 'what')
data.set('setting', 'what')
for (const p of data) {
console.log(p)
}
// console.log(data.entries())
// console.log(data.getAll())
fetch('/api/upload', {
method: 'POST',
headers: {
accepts: 'application/json',
},
body: data,
})
.then((res) => res.json())
.then((res) => {
pre.textContent = res.join('\n')
})
})

15
src/util/history.js Normal file
View File

@ -0,0 +1,15 @@
export function getCurrentUrl () {
const url = typeof location !== 'undefined' ? location : EMPTY
return `${url.pathname || ''}${url.search || ''}`
}
export function setUrl (url, state = null, type = 'push') {
if (typeof history !== 'undefined' && history[`${type}State`]) {
history[`${type}State`](state, null, url)
}
}
export function store (data) {
setUrl(getCurrentUrl(), Object.assign({}, window.history.state, data), 'replace')
}

49
webpack.config.js Normal file
View File

@ -0,0 +1,49 @@
'use strict'
const path = require('path')
// const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const outputDir = path.resolve(__dirname, 'public')
module.exports = {
mode: 'development',
entry: './src/index.jsx',
resolve: {
extensions: ['.js', '.jsx', '.mjs', '.json'],
},
output: {
path: outputDir,
filename: 'js/bundle.js',
},
module: {
rules: [{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [ {
loader: 'css-loader',
options: { importLoaders: 1 },
}, 'postcss-loader' ],
}),
}, {
test: /\.(mjs|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
}],
},
plugins: [
new ExtractTextPlugin({
filename: 'css/bundle.css',
allChunks: true,
}),
],
}