Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions client/src/views/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const VALIDATE_URL = import.meta.env.REACT_APP_VALIDATE_URL;
const TARGET_URL_PLACEHOLDER = 'http://code.jquery.com/jquery-1.9.1.min.js';

interface ExampleProps {
name: string,
url: string,
version: string,
onClick: React.MouseEventHandler<HTMLAnchorElement>,
name: string;
url: string;
version: string;
onClick: React.MouseEventHandler<HTMLAnchorElement>;
}

function Example({name, url, version, onClick}: ExampleProps) {
function Example({ name, url, version, onClick }: ExampleProps) {
return (
<li>
<a href={url} onClick={onClick}>
Expand Down Expand Up @@ -51,13 +51,12 @@ export default function Home() {
const [isLoading, setIsLoading] = useState(false);

const navigate = useNavigate();

const handleSubmit = useCallback(() => {
const url = encodeURIComponent(targetUrl || TARGET_URL_PLACEHOLDER);
const url = targetUrl || TARGET_URL_PLACEHOLDER;

setIsLoading(true);

// Encode the url again to match whats saved on the server
fetch(`${VALIDATE_URL}?url=${encodeURIComponent(url)}`, {
method: 'POST'
}).then(response => {
Expand All @@ -74,10 +73,13 @@ export default function Home() {
return (
<div>
<div className="row">
<form action="/validate" onSubmit={(event: React.FormEvent) => {
event.preventDefault();
handleSubmit();
}}>
<form
action="/validate"
onSubmit={(event: React.FormEvent) => {
event.preventDefault();
handleSubmit();
}}
>
<div className="col-md-10 form-group">
<input
type="text"
Expand All @@ -101,7 +103,7 @@ export default function Home() {
<Example
key={index}
{...example}
onClick={(event) => {
onClick={event => {
event.preventDefault();

setTargetUrl(example.url);
Expand Down
6 changes: 4 additions & 2 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Sentry.init({
import path from 'path';
import { Request, Response } from 'express';
import { Storage } from '@google-cloud/storage';
import _validateGeneratedFile from './lib/validateGeneratedFile';
import { validateMinifiedFileAtUrl } from './lib/validateGeneratedFile';

let config: { [key: string]: string } = {};
try {
Expand Down Expand Up @@ -53,7 +53,7 @@ export function validateGeneratedFile(req: Request, res: Response) {

Sentry.setTag('sourcemap_url', url);

_validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
const bucket = storage.bucket(config.STORAGE_BUCKET);

// object names can't contain most symbols, so encode as a URI component
Expand All @@ -66,10 +66,12 @@ export function validateGeneratedFile(req: Request, res: Response) {
contentType: 'text/plain; charset=utf-8'
}
});

stream.on('error', async err => {
res.status(500).send(err.message);
Sentry.captureException(err);
});

stream.on('finish', async () => {
res.status(200).send(encodeURIComponent(objectName));
});
Expand Down
40 changes: 20 additions & 20 deletions server/src/lib/__tests__/validateGeneratedFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path';
import nock from 'nock';

import validateGeneratedFile from '../validateGeneratedFile';
import { validateMinifiedFileAtUrl } from '../validateGeneratedFile';

const {
HOST,
Expand All @@ -27,7 +27,7 @@ it('should download the target minified file, source maps, and external source f
.get('/static/two.js')
.reply(200, TWO_JS);

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
// verify all mocked requests satisfied
scope.done();

Expand All @@ -53,7 +53,7 @@ describe('source map location', () => {
.get('/static/app.js.map')
.reply(200, RAW_INLINE_SOURCE_MAP);

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -71,7 +71,7 @@ describe('source map location', () => {
.get(appPath)
.reply(200, fs.readFileSync(minFilePath, 'utf-8'));

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -85,7 +85,7 @@ describe('source map location', () => {
nock(HOST)
.get('/static/app.js.map')
.reply(200, RAW_INLINE_SOURCE_MAP);
validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -101,7 +101,7 @@ describe('source map location', () => {
.get('/static/app.js.map')
.reply(200, RAW_INLINE_SOURCE_MAP);

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -118,7 +118,7 @@ describe('source map location', () => {
.get('/static/app.js.map')
.reply(200, RAW_INLINE_SOURCE_MAP);

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -129,7 +129,7 @@ describe('source map location', () => {
.get(appPath)
.reply(200, 'function(){}();');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('SourceMapNotFoundError');
done();
Expand All @@ -144,7 +144,7 @@ describe('http failures', () => {
.socketDelay(5001)
.reply(200, '<html></html>');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('ResourceTimeoutError');
expect(report.errors[0]).toHaveProperty(
Expand All @@ -164,7 +164,7 @@ describe('http failures', () => {
.get('/static/app.js.map')
.socketDelay(5001)
.reply(200, RAW_DEFAULT_SOURCE_MAP);
validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('ResourceTimeoutError');
expect(report.errors[0]).toHaveProperty(
Expand All @@ -180,7 +180,7 @@ describe('http failures', () => {
.get(appPath)
.reply(401, 'Not Authenticated');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('UnableToFetchMinifiedError');
done();
Expand All @@ -198,7 +198,7 @@ describe('http failures', () => {
port: 1337
});

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('ConnectionRefusedError');
done();
Expand All @@ -214,7 +214,7 @@ describe('http failures', () => {
.get('/static/app.js.map')
.reply(401, 'Not Authenticated');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('UnableToFetchSourceMapError');
done();
Expand All @@ -232,7 +232,7 @@ describe('http failures', () => {
.get('/static/two.js')
.reply(401, 'Not authenticated');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
// verify all mocked requests satisfied
scope.done();
expect(report.errors).toHaveLength(1);
Expand All @@ -252,7 +252,7 @@ describe('parsing failures', () => {
.get('/static/app.js.map')
.reply(200, '!@#(!*@#(*&@');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('InvalidJSONError');
expect(report.errors[0]).toHaveProperty(
Expand All @@ -272,7 +272,7 @@ describe('parsing failures', () => {
.get('/static/app.js.map')
.reply(200, '{"version":"3"}');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('InvalidSourceMapFormatError');
expect(report.errors[0]).toHaveProperty(
Expand All @@ -296,7 +296,7 @@ describe('content failures', () => {
.get('/static/two.js')
.reply(200, ' \n\n\n<!DOCTYPE html><html>lol</html>');

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
scope.done();
expect(report.errors).toHaveLength(1);
expect(report.errors[0].name).toBe('BadContentError');
Expand Down Expand Up @@ -327,7 +327,7 @@ describe('mappings', () => {
.get('/static/add.inlineSources.js.map')
.reply(200, fs.readFileSync(mapFilePath, 'utf-8'));

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).toHaveLength(0);
done();
});
Expand All @@ -349,7 +349,7 @@ describe('mappings', () => {
.get('/static/add.fuzzLines.js.map')
.reply(200, fs.readFileSync(mapFilePath, 'utf-8'));

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.errors).not.toHaveLength(0);
expect(report.errors[0].name).toBe('BadTokenError');
expect(report.errors[0]).toHaveProperty(
Expand All @@ -376,7 +376,7 @@ describe('mappings', () => {
.get('/static/add.fuzzColumns.js.map')
.reply(200, fs.readFileSync(mapFilePath, 'utf-8'));

validateGeneratedFile(url, report => {
validateMinifiedFileAtUrl(url, report => {
expect(report.warnings).not.toHaveLength(0);
expect(report.warnings[0].name).toBe('BadColumnError');
expect(report.warnings[0]).toHaveProperty(
Expand Down
14 changes: 13 additions & 1 deletion server/src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,17 @@ class BadColumnError extends BadTokenError {
}
}

class UnknownError extends Error {
resolutions: Array<string>;

constructor(url: string) {
super();
this.name = 'UnknownError';
this.message = `An unknown error occurred for url: ${url}`;
this.resolutions = ['Try again.'];
}
}

export {
SourceMapNotFoundError,
UnableToFetchError,
Expand All @@ -182,5 +193,6 @@ export {
BadContentError,
BadColumnError,
ResourceTimeoutError,
ConnectionRefusedError as SocketRefusedError
ConnectionRefusedError as SocketRefusedError,
UnknownError
};
15 changes: 10 additions & 5 deletions server/src/lib/validateGeneratedFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,39 @@ import {
SourceMapNotFoundError,
UnableToFetchMinifiedError,
ResourceTimeoutError,
SocketRefusedError
SocketRefusedError,
UnknownError
} from './errors';
import { MAX_TIMEOUT } from './constants';
import { resolveUrl, getSourceMapLocation } from './utils';
import { setTag } from '@sentry/node';

/**
* Validates a target transpiled/minified file located at a given url
* @param {string} url The target URL of the generated (transpiled) file,
* e.g. https://example.com/static/app.min.js
* @param {function} callback Invoked when validation is finished, passed a Report object
*/
export default function validateGeneratedFile(
export function validateMinifiedFileAtUrl(
url: string,
callback: (report: Report) => void
) {
const report = new Report({ url });

request(url, { timeout: MAX_TIMEOUT }, (error, response, body) => {
if (error) {
setTag('outgoing_request_had_error', true);

if (error.message === 'ESOCKETTIMEDOUT') {
report.pushError(new ResourceTimeoutError(url, MAX_TIMEOUT));
return void callback(report);
} else if (error.code === 'ECONNREFUSED') {
report.pushError(new SocketRefusedError(url));
return void callback(report);
} else {
report.pushError(new UnknownError(url));
}

return void console.error(error);
callback(report);
return;
}

if (response && response.statusCode !== 200) {
Expand Down
9 changes: 6 additions & 3 deletions server/src/lib/validateSourceMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
InvalidSourceMapFormatError,
InvalidJSONError,
BadContentError,
ResourceTimeoutError
ResourceTimeoutError,
UnknownError
} from './errors';

import { MAX_TIMEOUT } from './constants';
Expand Down Expand Up @@ -70,9 +71,11 @@ export default function validateSourceMap(
if (error) {
if (error.message === 'ESOCKETTIMEDOUT') {
report.pushError(new ResourceTimeoutError(sourceMapUrl, MAX_TIMEOUT));
return void reportCallback(report);
} else {
report.pushError(new UnknownError(sourceMapUrl));
}
return void console.error(error);
reportCallback(report);
return;
}

if (response && response.statusCode !== 200) {
Expand Down