pull/152/head
Alex Kern 5 years ago
parent b830677033
commit 5c3306b9c6
No known key found for this signature in database
GPG Key ID: F3141D5EDF48F89F

@ -1,6 +1,9 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
parserOptions: {
project: './tsconfig.json'
},
extends: [ extends: [
'@strv/typescript', '@strv/typescript',
'@strv/typescript/optional', '@strv/typescript/optional',

@ -50,7 +50,9 @@
"@types/node": "^14.11.1", "@types/node": "^14.11.1",
"@types/react": "^16.9.49", "@types/react": "^16.9.49",
"@typescript-eslint/eslint-plugin": "^4.1.1", "@typescript-eslint/eslint-plugin": "^4.1.1",
"@typescript-eslint/parser": "^4.1.1",
"eslint": "^7.9.0", "eslint": "^7.9.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"prettier": "^2.1.2", "prettier": "^2.1.2",

@ -1,9 +1,8 @@
import alt from '../alt' import alt from '../alt';
export default alt.createActions(class DownloadActions { export default alt.createActions(class DownloadActions {
constructor() { constructor() {
this.generateActions( this.generateActions('requestDownload');
'requestDownload' }
) },
} )
})

@ -1,10 +1,8 @@
import alt from '../alt' import alt from '../alt';
export default alt.createActions(class SupportActions { export default alt.createActions(class SupportActions {
constructor() { constructor() {
this.generateActions( this.generateActions('isChrome', 'noSupport');
'isChrome', }
'noSupport' },
) )
}
})

@ -1,9 +1,8 @@
import alt from '../alt' import alt from '../alt';
export default alt.createActions(class UploadActions { export default alt.createActions(class UploadActions {
constructor() { constructor() {
this.generateActions( this.generateActions('uploadFile');
'uploadFile' }
) },
} )
})

@ -1,2 +1,3 @@
import Alt from 'alt' import Alt from 'alt';
export default new Alt() export default new Alt()

@ -2,21 +2,24 @@ import "babel-polyfill";
import "./index.styl"; import "./index.styl";
import React from "react"; import React from "react";
import ReactRouter from "react-router"; import ReactRouter from "react-router";
import routes from "./routes"; import webrtcSupport from 'webrtcsupport';
import alt from "./alt"; import routes from './routes';
import webrtcSupport from "webrtcsupport"; import alt from './alt';
import SupportActions from "./actions/SupportActions"; import SupportActions from "./actions/SupportActions";
let bootstrap = document.getElementById("bootstrap").innerHTML; const bootstrap = document.getElementById("bootstrap").innerHTML;
alt.bootstrap(bootstrap); alt.bootstrap(bootstrap);
window.FilePizza = () => { window.FilePizza = () => {
ReactRouter.run(routes, ReactRouter.HistoryLocation, function(Handler) { ReactRouter.run(routes, ReactRouter.HistoryLocation, Handler => {
React.render(<Handler data={bootstrap} />, document); React.render(<Handler data={bootstrap} />, document);
}); })
if (!webrtcSupport.support) {
SupportActions.noSupport();
}
if (!webrtcSupport.support) SupportActions.noSupport(); const isChrome = navigator.userAgent.toLowerCase().includes('chrome')
;if (isChrome) {
let isChrome = navigator.userAgent.toLowerCase().indexOf("chrome") > -1; SupportActions.isChrome()
if (isChrome) SupportActions.isChrome(); }
}; };

@ -1,14 +1,14 @@
import Bootstrap from "./Bootstrap";
import ErrorPage from "./ErrorPage";
import FrozenHead from "react-frozenhead"; import FrozenHead from "react-frozenhead";
import React from "react"; import React from "react";
import SupportStore from "../stores/SupportStore"; import { RouteHandler } from 'react-router';
import { RouteHandler } from "react-router"; import ga from 'react-google-analytics';
import ga from "react-google-analytics"; import SupportStore from '../stores/SupportStore';
import ErrorPage from "./ErrorPage";
import Bootstrap from './Bootstrap';
if (process.env.GA_ACCESS_TOKEN) { if (process.env.GA_ACCESS_TOKEN) {
ga("create", process.env.GA_ACCESS_TOKEN, "auto"); ga("create", process.env.GA_ACCESS_TOKEN, "auto");
ga("send", "pageview"); ;;ga('send', 'pageview')
} }
export default class App extends React.Component { export default class App extends React.Component {
@ -18,7 +18,7 @@ export default class App extends React.Component {
this._onChange = () => { this._onChange = () => {
this.setState(SupportStore.getState()); this.setState(SupportStore.getState());
}; }
} }
componentDidMount() { componentDidMount() {
@ -62,7 +62,13 @@ export default class App extends React.Component {
</div> </div>
<footer className="footer"> <footer className="footer">
<p> <p>
<strong>Like FilePizza?</strong> Support its development! <a href="https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22" className="donate-button">donate</a> <strong>Like FilePizza?</strong> Support its development!{' '}
<a
href="https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22"
className="donate-button"
>
donate
</a>
</p> </p>
<p className="byline"> <p className="byline">
@ -85,7 +91,7 @@ export default class App extends React.Component {
</p> </p>
</footer> </footer>
<script>FilePizza()</script> <script>FilePizza()</script>
{ process.env.GA_ACCESS_TOKEN ? <ga.Initializer /> : <div></div> } {process.env.GA_ACCESS_TOKEN ? <ga.Initializer /> : <div></div>}
</body> </body>
</html> </html>
); );

@ -1,12 +1,13 @@
import React from 'react' import React from 'react';
export default class Bootstrap extends React.Component { export default class Bootstrap extends React.Component {
render() { render() {
return <script return (
id="bootstrap" <script
type="application/json" id="bootstrap"
dangerouslySetInnerHTML={{ __html: this.props.data}} /> type="application/json"
dangerouslySetInnerHTML={{ __html: this.props.data }}
/>
)
} }
} }

@ -1,4 +1,4 @@
import React from 'react' import React from 'react';
export default class DownloadButton extends React.Component { export default class DownloadButton extends React.Component {
constructor() { constructor() {
@ -11,15 +11,14 @@ export default class DownloadButton extends React.Component {
} }
render() { render() {
return <button return (
className="download-button" <button className="download-button" onClick={this.onClick}>
onClick={this.onClick}> Download
Download </button>
</button> )
} }
} }
DownloadButton.propTypes = { DownloadButton.propTypes = {
onClick: React.PropTypes.func.isRequired onClick: React.PropTypes.func.isRequired,
} }

@ -1,22 +1,21 @@
import ChromeNotice from './ChromeNotice' import React from "react";
import DownloadActions from '../actions/DownloadActions' import DownloadActions from '../actions/DownloadActions';
import DownloadButton from './DownloadButton' import DownloadStore from '../stores/DownloadStore';
import DownloadStore from '../stores/DownloadStore' import { formatSize } from "../util";
import ErrorPage from './ErrorPage' import ChromeNotice from './ChromeNotice';
import ProgressBar from './ProgressBar' import DownloadButton from "./DownloadButton";
import React from 'react' import ErrorPage from './ErrorPage';
import Spinner from './Spinner' import ProgressBar from './ProgressBar';
import { formatSize } from '../util' import Spinner from "./Spinner";
export default class DownloadPage extends React.Component { export default class DownloadPage extends React.Component {
constructor() { constructor() {
super() super()
this.state = DownloadStore.getState() this.state = DownloadStore.getState()
this._onChange = () => { this._onChange = () => {
this.setState(DownloadStore.getState()) this.setState(DownloadStore.getState())
} };
this.downloadFile = this.downloadFile.bind(this) this.downloadFile = this.downloadFile.bind(this)
} }
@ -36,51 +35,69 @@ export default class DownloadPage extends React.Component {
render() { render() {
switch (this.state.status) { switch (this.state.status) {
case 'ready': case 'ready':
return <div className="page"> return (
<div className="page">
<h1>FilePizza</h1> <h1>FilePizza</h1>
<Spinner dir="down" <Spinner
name={this.state.fileName} dir="down"
size={this.state.fileSize} /> name={this.state.fileName}
size={this.state.fileSize}
<ChromeNotice /> />
<p className="notice">Peers: {this.state.peers} &middot; Up: {formatSize(this.state.speedUp)} &middot; Down: {formatSize(this.state.speedDown)}</p>
<DownloadButton onClick={this.downloadFile} /> <ChromeNotice />
<p className="notice">
</div> Peers: {this.state.peers} &middot; Up:{' '}
{formatSize(this.state.speedUp)} &middot; Down:{' '}
{formatSize(this.state.speedDown)}
</p>
<DownloadButton onClick={this.downloadFile} />
</div>
)
case 'requesting': case 'requesting':
case 'downloading': case 'downloading':
return <div className="page"> return (
<div className="page">
<h1>FilePizza</h1> <h1>FilePizza</h1>
<Spinner dir="down" animated <Spinner
name={this.state.fileName} dir="down"
size={this.state.fileSize} /> animated
name={this.state.fileName}
<ChromeNotice /> size={this.state.fileSize}
<p className="notice">Peers: {this.state.peers} &middot; Up: {formatSize(this.state.speedUp)} &middot; Down: {formatSize(this.state.speedDown)}</p> />
<ProgressBar value={this.state.progress} />
<ChromeNotice />
</div> <p className="notice">
Peers: {this.state.peers} &middot; Up:{' '}
{formatSize(this.state.speedUp)} &middot; Down:{' '}
{formatSize(this.state.speedDown)}
</p>
<ProgressBar value={this.state.progress} />
</div>
)
case 'done': case 'done':
return <div className="page"> return (
<div className="page">
<h1>FilePizza</h1> <h1>FilePizza</h1>
<Spinner dir="down" <Spinner
name={this.state.fileName} dir="down"
size={this.state.fileSize} /> name={this.state.fileName}
size={this.state.fileSize}
<ChromeNotice /> />
<p className="notice">Peers: {this.state.peers} &middot; Up: {formatSize(this.state.speedUp)} &middot; Down: {formatSize(this.state.speedDown)}</p>
<ProgressBar value={1} /> <ChromeNotice />
<p className="notice">
</div> Peers: {this.state.peers} &middot; Up:{' '}
{formatSize(this.state.speedUp)} &middot; Down:{' '}
{formatSize(this.state.speedDown)}
</p>
<ProgressBar value={1} />
</div>
)
default: default:
return <ErrorPage /> return <ErrorPage />
} }
} }
} }

@ -1,7 +1,6 @@
import React from 'react' import React from 'react';
export default class DropZone extends React.Component { export default class DropZone extends React.Component {
constructor() { constructor() {
super() super()
this.state = { focus: false } this.state = { focus: false }
@ -17,41 +16,49 @@ export default class DropZone extends React.Component {
} }
onDragLeave(e) { onDragLeave(e) {
if (e.target !== this.refs.overlay.getDOMNode()) return if (e.target !== this.refs.overlay.getDOMNode()) {
return
}
this.setState({ focus: false }) this.setState({ focus: false })
} }
onDragOver(e) { onDragOver(e) {
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'copy' e.dataTransfer.dropEffect = 'copy';
} }
onDrop(e) { onDrop(e) {
e.preventDefault() e.preventDefault()
this.setState({ focus: false }) this.setState({ focus: false })
let file = e.dataTransfer.files[0] const file = e.dataTransfer.files[0]
if (this.props.onDrop && file) this.props.onDrop(file) if (this.props.onDrop && file) {
this.props.onDrop(file)
}
} }
render() { render() {
return <div className="drop-zone" ref="root" return (
onDragEnter={this.onDragEnter} <div
onDragLeave={this.onDragLeave} className="drop-zone"
onDragOver={this.onDragOver} ref="root"
onDrop={this.onDrop}> onDragEnter={this.onDragEnter}
onDragLeave={this.onDragLeave}
<div className="drop-zone-overlay" onDragOver={this.onDragOver}
hidden={!this.state.focus} onDrop={this.onDrop}
ref="overlay" /> >
<div
className="drop-zone-overlay"
hidden={!this.state.focus}
ref="overlay"
/>
{this.props.children} {this.props.children}
</div>
</div> )
} }
} }
DropZone.propTypes = { DropZone.propTypes = {
onDrop: React.PropTypes.func.isRequired onDrop: React.PropTypes.func.isRequired,
} }

@ -1,16 +1,15 @@
import ErrorStore from '../stores/ErrorStore' import React from "react";
import React from 'react' import ErrorStore from "../stores/ErrorStore";
import Spinner from './Spinner' import Spinner from './Spinner';
export default class ErrorPage extends React.Component { export default class ErrorPage extends React.Component {
constructor() { constructor() {
super() super()
this.state = ErrorStore.getState() this.state = ErrorStore.getState()
this._onChange = () => { this._onChange = () => {
this.setState(ErrorStore.getState()) this.setState(ErrorStore.getState())
} };
} }
componentDidMount() { componentDidMount() {
@ -22,20 +21,17 @@ export default class ErrorPage extends React.Component {
} }
render() { render() {
return <div className="page"> return (
<div className="page">
<Spinner dir="up" /> <Spinner dir="up" />
<h1 className="with-subtitle">FilePizza</h1> <h1 className="with-subtitle">FilePizza</h1>
<p className="subtitle"> <p className="subtitle">
<strong>{this.state.status}:</strong> {this.state.message} <strong>{this.state.status}:</strong> {this.state.message}
</p> </p>
{this.state.stack {this.state.stack ? <pre>{this.state.stack}</pre> : null}
? <pre>{this.state.stack}</pre> </div>
: null} )
</div>
} }
} }

@ -1,43 +1,42 @@
import React from 'react' import React from "react";
import classnames from 'classnames' import classnames from "classnames";
function formatProgress(dec) { function formatProgress(dec) {
return (dec * 100).toPrecision(3) + "%" return `${(dec * 100).toPrecision(3) }%`
} }
export default class ProgressBar extends React.Component { export default class ProgressBar extends React.Component {
render() { render() {
const failed = this.props.value < 0; const failed = this.props.value < 0
const inProgress = this.props.value < 1 && this.props.value >= 0; const inProgress = this.props.value < 1 && this.props.value >= 0
const classes = classnames('progress-bar', { const classes = classnames("progress-bar", {
'progress-bar-failed': failed, "progress-bar-failed": failed,
'progress-bar-in-progress': inProgress, "progress-bar-in-progress": inProgress,
'progress-bar-small': this.props.small "progress-bar-small": this.props.small,
}) });
const formatted = formatProgress(this.props.value) const formatted = formatProgress(this.props.value);
return <div className={classes}> return (
{failed <div className={classes}>
? <div className="progress-bar-text">Failed</div> {failed ? (
: inProgress ? <div <div className="progress-bar-text">Failed</div>
className="progress-bar-inner" ) : inProgress ? (
style={{width: formatted}}> <div className="progress-bar-inner" style={{ width: formatted }}>
<div className="progress-bar-text"> <div className="progress-bar-text">{formatted}</div>
{formatted}
</div> </div>
</div> ) : (
: <div className="progress-bar-text">Delivered</div>} : <div className="progress-bar-text">Delivered</div>}
</div> </div>
);
} }
} }
ProgressBar.propTypes = { ProgressBar.propTypes = {
value: React.PropTypes.number.isRequired, value: React.PropTypes.number.isRequired,
small: React.PropTypes.bool small: React.PropTypes.bool,
} };
ProgressBar.defaultProps = { ProgressBar.defaultProps = {
small: false small: false,
} };

@ -1,38 +1,41 @@
import React from 'react' import React from "react";
import classnames from 'classnames' import classnames from "classnames";
import { formatSize } from '../util' import { formatSize } from "../util";
export default class Spinner extends React.Component { export default class Spinner extends React.Component {
render() { render() {
const classes = classnames('spinner', { const classes = classnames("spinner", {
'spinner-animated': this.props.animated "spinner-animated": this.props.animated,
}) });
return <div className={classes}> return (
<img <div className={classes}>
alt={this.props.name || this.props.dir} <img
src={`/images/${this.props.dir}.png`} alt={this.props.name || this.props.dir}
className="spinner-image" /> src={`/images/${this.props.dir}.png`}
className="spinner-image"
/>
{this.props.name === null ? null {this.props.name === null ? null
: <div className="spinner-name">{this.props.name}</div>} : <div className="spinner-name">{this.props.name}</div>
{this.props.size === null ? null )}
: <div className="spinner-size">{formatSize(this.props.size)}</div>} {this.props.size === null ? null
</div> : <div className="spinner-size">{formatSize(this.props.size)}</div>
)}
</div>
);
} }
} }
Spinner.propTypes = { Spinner.propTypes = {
dir: React.PropTypes.oneOf(['up', 'down']).isRequired, dir: React.PropTypes.oneOf(["up", "down"]).isRequired,
name: React.PropTypes.string, name: React.PropTypes.string,
size: React.PropTypes.number, size: React.PropTypes.number,
animated: React.PropTypes.bool animated: React.PropTypes.bool,
} };
Spinner.defaultProps = { Spinner.defaultProps = {
name: null, name: null,
size: null, size: null,
animated: false animated: false,
} };

@ -1,5 +1,5 @@
import React from 'react' import React from 'react';
import QRCode from 'react-qr' import QRCode from 'react-qr';
export default class Tempalink extends React.Component { export default class Tempalink extends React.Component {
constructor() { constructor() {
@ -12,27 +12,25 @@ export default class Tempalink extends React.Component {
} }
render() { render() {
var url = window.location.origin + '/' + this.props.token const url = `${window.location.origin}/${this.props.token}`;
var shortUrl = window.location.origin + '/download/' + this.props.shortToken const shortUrl
= window.location.origin + '/download/' + this.props.shortToken
return <div className="tempalink"> return (
<div className="qr"> <div className="tempalink">
<QRCode text={url} /> <div className="qr">
</div> <QRCode text={url} />
<div className="urls">
<div className="long-url">
<input
onClick={this.onClick}
readOnly
type="text"
value={url} />
</div> </div>
<div className="urls">
<div className="long-url">
<input onClick={this.onClick} readOnly type="text" value={url} />
</div>
<div className="short-url"> <div className="short-url">
or, for short: <span>{shortUrl}</span> or, for short: <span>{shortUrl}</span>
</div>
</div> </div>
</div> </div>
</div> )
} }
} }

@ -1,90 +1,104 @@
import DropZone from './DropZone'
import React from 'react' import React from 'react'
import Spinner from './Spinner' import DropZone from './DropZone'
import Tempalink from './Tempalink' import Spinner from "./Spinner";
import UploadActions from '../actions/UploadActions' import Tempalink from "./Tempalink";
import UploadStore from '../stores/UploadStore' import UploadActions from "../actions/UploadActions";
import socket from 'filepizza-socket' import UploadStore from "../stores/UploadStore";
import { formatSize } from '../util' import socket from "filepizza-socket";
import { formatSize } from "../util";
export default class UploadPage extends React.Component { export default class UploadPage extends React.Component {
constructor() { constructor() {
super() super();
this.state = UploadStore.getState() this.state = UploadStore.getState();
this._onChange = () => { this._onChange = () => {
this.setState(UploadStore.getState()) this.setState(UploadStore.getState());
} };
this.uploadFile = this.uploadFile.bind(this) this.uploadFile = this.uploadFile.bind(this);
} }
componentDidMount() { componentDidMount() {
UploadStore.listen(this._onChange) UploadStore.listen(this._onChange);
} }
componentWillUnmount() { componentWillUnmount() {
UploadStore.unlisten(this._onChange) UploadStore.unlisten(this._onChange);
} }
uploadFile(file) { uploadFile(file) {
UploadActions.uploadFile(file) UploadActions.uploadFile(file);
} }
handleSelectedFile(event) { handleSelectedFile(event) {
let files = event.target.files const files = event.target.files;
if (files.length > 0) { if (files.length > 0) {
UploadActions.uploadFile(files[0]) UploadActions.uploadFile(files[0]);
} }
} }
render() { render() {
switch (this.state.status) { switch (this.state.status) {
case 'ready': case "ready":
return (
return <DropZone onDrop={this.uploadFile}> <DropZone onDrop={this.uploadFile}>
<div className="page">
<Spinner dir="up" />
<h1>FilePizza</h1>
<p>Free peer-to-peer file transfers in your browser.</p>
<small className="notice">
We never store anything. Files only served fresh.
</small>
<p>
<label className="select-file-label">
<input
type="file"
onChange={this.handleSelectedFile}
required
/>
<span>select a file</span>
</label>
</p>
</div>
</DropZone>
);
case "processing":
return (
<div className="page"> <div className="page">
<Spinner dir="up" animated />
<Spinner dir="up" /> <h1>FilePizza</h1>
<p>Processing...</p>
</div>
);
case "uploading":
return (
<div className="page">
<h1>FilePizza</h1> <h1>FilePizza</h1>
<p>Free peer-to-peer file transfers in your browser.</p> <Spinner
<small className="notice">We never store anything. Files only served fresh.</small> dir="up"
animated
name={this.state.fileName}
size={this.state.fileSize}
/>
<p>Send someone this link to download.</p>
<small className="notice">
This link will work as long as this page is open.
</small>
<p> <p>
<label className="select-file-label"> Peers: {this.state.peers} &middot; Up:{" "}
<input type="file" onChange={this.handleSelectedFile} required/> {formatSize(this.state.speedUp)}
<span>select a file</span>
</label>
</p> </p>
<Tempalink
token={this.state.token}
shortToken={this.state.shortToken}
/>
</div> </div>
</DropZone> );
case 'processing':
return <div className="page">
<Spinner dir="up" animated />
<h1>FilePizza</h1>
<p>Processing...</p>
</div>
case 'uploading':
return <div className="page">
<h1>FilePizza</h1>
<Spinner dir="up" animated
name={this.state.fileName}
size={this.state.fileSize} />
<p>Send someone this link to download.</p>
<small className="notice">This link will work as long as this page is open.</small>
<p>Peers: {this.state.peers} &middot; Up: {formatSize(this.state.speedUp)}</p>
<Tempalink token={this.state.token} shortToken={this.state.shortToken} />
</div>
} }
}
} }

@ -15,27 +15,32 @@ export default class UploadPage extends React.Component {
render() { render() {
switch (this.props.status) { switch (this.props.status) {
case 'ready': case 'ready':
return <div> return (
<DropZone onDrop={this.uploadFile} /> <div>
<Arrow dir="up" /> <DropZone onDrop={this.uploadFile} />
</div>; <Arrow dir="up" />
break; </div>
)
break
case 'processing': case 'processing':
return <div> return (
<Arrow dir="up" animated /> <div>
<FileDescription file={this.props.file} /> <Arrow dir="up" animated />
</div>; <FileDescription file={this.props.file} />
break; </div>
)
break
case 'uploading': case 'uploading':
return <div> return (
<Arrow dir="up" animated /> <div>
<FileDescription file={this.props.file} /> <Arrow dir="up" animated />
<Temaplink token={this.props.token} /> <FileDescription file={this.props.file} />
</div>; <Temaplink token={this.props.token} />
break; </div>
)
break
} }
} }
} }

@ -1,46 +1,44 @@
import toppings from './toppings' import xkcdPassword from "xkcd-password";
import xkcdPassword from 'xkcd-password' import toppings from "./toppings";
const TOKEN_OPTIONS = { const TOKEN_OPTIONS = {
numWords: 4, numWords: 4,
minLength: 3, minLength: 3,
maxLength: 20 maxLength: 20,
} }
const SHORT_TOKEN_OPTIONS = { const SHORT_TOKEN_OPTIONS = {
length: 8, length: 8,
chars: '0123456789abcdefghijklmnopqrstuvwxyz' chars: '0123456789abcdefghijklmnopqrstuvwxyz',
} }
let tokens = {} const tokens = {}
let shortTokens = {} const shortTokens = {}
const tokenGenerator = new xkcdPassword() const tokenGenerator = new xkcdPassword()
tokenGenerator.initWithWordList(toppings) tokenGenerator.initWithWordList(toppings)
function generateShortToken() { function generateShortToken() {
var result = ''; let result = '';
for (var i = SHORT_TOKEN_OPTIONS.length; i > 0; --i) for (let i = SHORT_TOKEN_OPTIONS.length; i > 0; --i) {
result += SHORT_TOKEN_OPTIONS.chars[Math.floor(Math.random() * SHORT_TOKEN_OPTIONS.chars.length)]; { result += SHORT_TOKEN_OPTIONS.chars[Math.floor(Math.random() * SHORT_TOKEN_OPTIONS.chars.length)]}
return result; return result;
} }
export function create(socket) { export function create(socket) {
return tokenGenerator.generate(TOKEN_OPTIONS).then((parts) => { return tokenGenerator.generate(TOKEN_OPTIONS).then((parts) => {
const token = parts.join('/') const token = parts.join('/')
const shortToken = generateShortToken() ;const shortToken = generateShortToken()
let result = { const result = {
token: token, token,
shortToken: shortToken, shortToken,
socket: socket socket,
} }
tokens[token] = result tokens[token] = result
shortTokens[shortToken] = result shortTokens[shortToken] = result
return result return result
}) });
} }
export function find(token) { export function find(token) {
@ -52,7 +50,7 @@ export function findShort(shortToken) {
} }
export function remove(client) { export function remove(client) {
if (client == null) return if (client == null) { return }
delete tokens[client.token] delete tokens[client.token]
delete shortTokens[client.shortToken] delete shortTokens[client.shortToken]
} }

@ -1,48 +1,51 @@
var twilio = require("twilio"); const twilio = require("twilio");
var winston = require("winston"); var winston = require("winston");
if (process.env.TWILIO_SID && process.env.TWILIO_TOKEN) { if (process.env.TWILIO_SID && process.env.TWILIO_TOKEN) {
var twilioSID = process.env.TWILIO_SID; const twilioSID = process.env.TWILIO_SID;
var twilioToken = process.env.TWILIO_TOKEN; const twilioToken = process.env.TWILIO_TOKEN;
var client = twilio(twilioSID, twilioToken); var client = twilio(twilioSID, twilioToken);
winston.info("Using Twilio TURN service"); winston.info("Using Twilio TURN service");
} else { } else {
var client = null; var client = null;
} }
var ICE_SERVERS = [ let ICE_SERVERS = [
{ {
urls: "stun:stun.l.google.com:19302" urls: "stun:stun.l.google.com:19302",
} },
]; ];
if (process.env.ICE_SERVERS) { if (process.env.ICE_SERVERS) {
ICE_SERVERS = JSON.parse(process.env.ICE_SERVERS) ICE_SERVERS = JSON.parse(process.env.ICE_SERVERS)
} }
var CACHE_LIFETIME = 5 * 60 * 1000; // 5 minutes const CACHE_LIFETIME = 5 * 60 * 1000; // 5 minutes
var cachedPromise = null; let cachedPromise = null;
function clearCache() { function clearCache() {
cachedPromise = null; cachedPromise = null;
} }
exports.getICEServers = function() { exports.getICEServers = function() {
if (client == null) return Promise.resolve(ICE_SERVERS); if (client == null) {
if (cachedPromise) return cachedPromise; return Promise.resolve(ICE_SERVERS)
}
if (cachedPromise) {
return cachedPromise
}
cachedPromise = new Promise(function(resolve, reject) { cachedPromise = new Promise((resolve, reject) => {
client.tokens.create({}, function(err, token) { client.tokens.create({}, (err, token) => {
if (err) { if (err) {
winston.error(err); winston.error(err);
return resolve(DEFAULT_ICE_SERVERS); return resolve(DEFAULT_ICE_SERVERS);
} }
winston.info("Retrieved ICE servers from Twilio"); winston.info("Retrieved ICE servers from Twilio");
setTimeout(clearCache, CACHE_LIFETIME); ;setTimeout(clearCache, CACHE_LIFETIME)
resolve(token.ice_servers); resolve(token.ice_servers);
}); })
}); });
return cachedPromise; return cachedPromise;
}; }

@ -2,7 +2,7 @@
try { try {
require('../newrelic') require('../newrelic')
require('newrelic') ;require('newrelic');
} catch (ex) { } catch (ex) {
// Don't load New Relic if the configuration file doesn't exist. // Don't load New Relic if the configuration file doesn't exist.
} }
@ -10,15 +10,14 @@ try {
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
p.catch(err => { p.catch(err => {
console.error('Exiting due to unhandled rejection!') console.error('Exiting due to unhandled rejection!')
console.error(err) ;;;console.error(err)
process.exit(1) process.exit(1)
}) });
}) })
process.on('uncaughtException', err => { process.on('uncaughtException', err => {
console.error('Exiting due to uncaught exception!') console.error('Exiting due to uncaught exception!')
console.error(err.stack) ;console.error(err.stack)
process.exit(1) process.exit(1)
}) });
module.exports = require("./server");
module.exports = require('./server')

@ -1,9 +1,7 @@
var db = require('../db') const express = require("express");
var express = require('express') const db = require("../db");
let routes = (module.exports = new express.Router())
var routes = module.exports = new express.Router() ;function bootstrap(uploader, req, res, next) {
function bootstrap (uploader, req, res, next) {
if (uploader) { if (uploader) {
res.locals.data = { res.locals.data = {
DownloadStore: { DownloadStore: {
@ -12,24 +10,23 @@ function bootstrap (uploader, req, res, next) {
fileSize: uploader.fileSize, fileSize: uploader.fileSize,
fileName: uploader.fileName, fileName: uploader.fileName,
fileType: uploader.fileType, fileType: uploader.fileType,
infoHash: uploader.infoHash infoHash: uploader.infoHash,
} },
} }
next() next()
} else { } else {
var err = new Error('Not Found') const err = new Error('Not Found')
err.status = 404 ;err.status = 404
next(err) next(err)
} }
} }
routes.get(/^\/([a-z]+\/[a-z]+\/[a-z]+\/[a-z]+)$/, function (req, res, next) { routes.get(/^\/([a-z]+\/[a-z]+\/[a-z]+\/[a-z]+)$/, (req, res, next) => {
var uploader = db.find(req.params[0]) const uploader = db.find(req.params[0])
return bootstrap(uploader, req, res, next) return bootstrap(uploader, req, res, next)
}) });
routes.get(/^\/download\/(\w+)$/, (req, res, next) => {
routes.get(/^\/download\/(\w+)$/, function (req, res, next) { const uploader = db.findShort(req.params[0])
var uploader = db.findShort(req.params[0])
return bootstrap(uploader, req, res, next) return bootstrap(uploader, req, res, next)
}) });

@ -1,19 +1,18 @@
module.exports = function (err, req, res, next) { module.exports = function (err, req, res, next) {
const status = err.status || 500;
const message = err.message || '';
const stack
= process.env.NODE_ENV === 'production' ? null : err.stack || null
var status = err.status || 500 req.url = '/error';
var message = err.message || ''
var stack = process.env.NODE_ENV === 'production' ? null : err.stack || null
req.url = '/error'
res.status(status) res.status(status)
res.locals.data = { res.locals.data = {
ErrorStore: { ErrorStore: {
status: status, status,
message: message, message,
stack: stack stack,
} },
} }
next() next()
};
}

@ -1,16 +1,15 @@
const path = require('path') const path = require('path')
;const BUNDLE_PATH = path.resolve(__dirname, '../../dist/bundle.js')
const BUNDLE_PATH = path.resolve(__dirname, '../../dist/bundle.js') ;if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production') {
module.exports = function (req, res) { module.exports = function (req, res) {
res.sendFile(BUNDLE_PATH) res.sendFile(BUNDLE_PATH)
} };
} else { } else {
const webpackMiddleware = require('webpack-dev-middleware') const webpackMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack') ;const webpack = require('webpack')
const config = require('../../webpack.config.js') ;const config = require('../../webpack.config.js')
config.output.filename = '/app.js' ;config.output.filename = '/app.js';
config.output.path = '/' config.output.path = '/';
module.exports = webpackMiddleware(webpack(config)) module.exports = webpackMiddleware(webpack(config))
} }

@ -1,30 +1,29 @@
var React = require('react') const React = require('react')
var ReactRouter = require('react-router') ;let ReactRouter = require('react-router')
var alt = require('../alt') ;let alt = require('../alt')
var routes = require('../routes') ;let routes = require('../routes')
;function isNotFound(state) {
function isNotFound(state) { for (const r of state.routes) {
for (var r of state.routes) { if (r.isNotFound) {
if (r.isNotFound) return true return true
}
} }
return false return false
} }
module.exports = function (req, res) { module.exports = function (req, res) {
alt.bootstrap(JSON.stringify(res.locals.data || {})) alt.bootstrap(JSON.stringify(res.locals.data || {}))
ReactRouter.run(routes, req.url, function (Handler, state) { ReactRouter.run(routes, req.url, (Handler, state) => {
const html = React.renderToString(<Handler data={alt.takeSnapshot()} />);
var html = React.renderToString(<Handler data={alt.takeSnapshot()} />)
alt.flush() alt.flush()
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
if (isNotFound(state)) res.status(404) ;;if (isNotFound(state)) {
res.status(404);
}
res.write('<!DOCTYPE html>\n') res.write('<!DOCTYPE html>\n')
res.end(html) ;;res.end(html)
});
})
} }

@ -1,6 +1,4 @@
var express = require('express') const path = require("path");
var path = require('path') const express = require("express");
let STATIC_PATH = path.resolve(__dirname, "../static");
var STATIC_PATH = path.resolve(__dirname, '../static') module.exports = express.static(STATIC_PATH);
module.exports = express.static(STATIC_PATH)

@ -1,10 +1,9 @@
import React from 'react' import React from 'react';
import { Route, DefaultRoute, NotFoundRoute, RouteHandler } from 'react-router' import { Route, DefaultRoute, NotFoundRoute, RouteHandler } from 'react-router';
import App from './components/App';
import App from './components/App' import DownloadPage from './components/DownloadPage';
import DownloadPage from './components/DownloadPage' import UploadPage from './components/UploadPage';
import UploadPage from './components/UploadPage' import ErrorPage from './components/ErrorPage';
import ErrorPage from './components/ErrorPage'
export default ( export default (
<Route handler={App}> <Route handler={App}>
@ -15,4 +14,3 @@ export default (
<NotFoundRoute handler={ErrorPage} /> <NotFoundRoute handler={ErrorPage} />
</Route> </Route>
) )

@ -1,35 +1,33 @@
var db = require("./db"); const fs = require("fs");
var express = require("express"); const express = require("express");
var expressWinston = require("express-winston"); var expressWinston = require("express-winston");
var fs = require("fs"); var ice = require('./ice')
var ice = require("./ice"); ;var socketIO = require('socket.io')
var socketIO = require("socket.io"); ;var winston = require('winston')
var winston = require("winston"); ;const db = require('./db')
;process.on("unhandledRejection", (reason, p) => {
process.on("unhandledRejection", (reason, p) => {
p.catch(err => { p.catch(err => {
log.error("Exiting due to unhandled rejection!"); log.error("Exiting due to unhandled rejection!");
log.error(err); log.error(err);
process.exit(1); process.exit(1);
}); })
}); });
process.on("uncaughtException", err => { process.on("uncaughtException", err => {
log.error("Exiting due to uncaught exception!"); log.error("Exiting due to uncaught exception!");
log.error(err); log.error(err);
process.exit(1); process.exit(1);
}); })
const app = express();
var app = express(); let port
var port = process.env.PORT || (process.env.NODE_ENV === "production" ? 80 : 3000);
process.env.PORT || (process.env.NODE_ENV === "production" ? 80 : 3000);
if (!process.env.QUIET) { if (!process.env.QUIET) {
app.use( app.use(
expressWinston.logger({ expressWinston.logger({
winstonInstance: winston, winstonInstance: winston,
expressFormat: true expressFormat: true,
}) })
); );
} }
@ -39,7 +37,7 @@ app.use(require("./middleware/static"));
app.use([ app.use([
require("./middleware/bootstrap"), require("./middleware/bootstrap"),
require("./middleware/error"), require("./middleware/error"),
require("./middleware/react") require("./middleware/react"),
]); ]);
const TRACKERS = process.env.WEBTORRENT_TRACKERS const TRACKERS = process.env.WEBTORRENT_TRACKERS
@ -47,18 +45,19 @@ const TRACKERS = process.env.WEBTORRENT_TRACKERS
: [ : [
["wss://tracker.openwebtorrent.com"], ["wss://tracker.openwebtorrent.com"],
["wss://tracker.btorrent.xyz"], ["wss://tracker.btorrent.xyz"],
["wss://tracker.fastcast.nz"] ['wss://tracker.fastcast.nz'],
]; ]
;function bootServer(server) {
function bootServer(server) { const io = socketIO(server);
var io = socketIO(server);
io.set("transports", ["polling"]); io.set("transports", ["polling"]);
io.on("connection", function(socket) { io.on("connection", socket => {
var upload = null; let upload = null;
socket.on("upload", function(metadata, res) { socket.on("upload", (metadata, res) => {
if (upload) return; if (upload) {
return
}
db.create(socket).then(u => { db.create(socket).then(u => {
upload = u; upload = u;
upload.fileName = metadata.fileName; upload.fileName = metadata.fileName;
@ -66,43 +65,45 @@ function bootServer(server) {
upload.fileType = metadata.fileType; upload.fileType = metadata.fileType;
upload.infoHash = metadata.infoHash; upload.infoHash = metadata.infoHash;
res({ token: upload.token, shortToken: upload.shortToken }); res({ token: upload.token, shortToken: upload.shortToken });
}); })
}); });
socket.on("trackerConfig", function(_, res) { socket.on("trackerConfig", (_, res) => {
ice.getICEServers().then(function(iceServers) { ice.getICEServers().then(iceServers => {
res({ rtcConfig: { iceServers }, announce: TRACKERS }); res({ rtcConfig: { iceServers }, announce: TRACKERS });
}); })
}); });
socket.on("disconnect", function() { socket.on("disconnect", () => {
db.remove(upload); db.remove(upload);
}); })
}); });
server.on("error", function(err) { server.on("error", err => {
winston.error(err.message); winston.error(err.message);
process.exit(1); process.exit(1);
}); })
server.listen(port, (err) => {
server.listen(port, function(err) { const host = server.address().address;
var host = server.address().address; const port = server.address().port;
var port = server.address().port;
winston.info("FilePizza listening on %s:%s", host, port); winston.info("FilePizza listening on %s:%s", host, port);
}); })
} }
if (process.env.HTTPS_KEY && process.env.HTTPS_CERT) { if (process.env.HTTPS_KEY && process.env.HTTPS_CERT) {
// user-supplied HTTPS key/cert // user-supplied HTTPS key/cert
var https = require("https"); const https = require("https");
var server = https.createServer({ var server = https.createServer(
key: fs.readFileSync(process.env.HTTPS_KEY), {
cert: fs.readFileSync(process.env.HTTPS_CERT), key: fs.readFileSync(process.env.HTTPS_KEY),
}, app) cert: fs.readFileSync(process.env.HTTPS_CERT),
},
app,
)
bootServer(server) bootServer(server)
} else { } else {
// no HTTPS // no HTTPS
var http = require("http"); const http = require("http");
var server = http.Server(app) var server = http.Server(app)
bootServer(server) bootServer(server)
} }

@ -1,73 +1,82 @@
import DownloadActions from '../actions/DownloadActions' import socket from "filepizza-socket";
import alt from '../alt' import DownloadActions from "../actions/DownloadActions";
import socket from 'filepizza-socket' import alt from "../alt";
import { getClient } from '../wt' import { getClient } from '../wt';
const SPEED_REFRESH_TIME = 2000 const SPEED_REFRESH_TIME = 2000
function downloadBlobURL(name, blobURL) { function downloadBlobURL(name, blobURL) {
let a = document.createElement('a') const a = document.createElement('a')
document.body.appendChild(a) ;document.body.appendChild(a)
a.download = name a.download = name
a.href = blobURL a.href = blobURL
a.click() a.click()
} }
export default alt.createStore(class DownloadStore { export default alt.createStore(
class DownloadStore {
constructor() {
this.bindActions(DownloadActions)
constructor() { this.fileName = "";
this.bindActions(DownloadActions) this.fileSize = 0
this.fileType = "";
this.infoHash = null
this.peers = 0;
this.progress = 0;
this.speedDown = 0;
this.speedUp = 0;
this.status = "uninitialized";
this.token = null
}
this.fileName = '' onRequestDownload() {
this.fileSize = 0 if (this.status !== 'ready') {
this.fileType = '' return
this.infoHash = null }
this.peers = 0 this.status = 'requesting';
this.progress = 0
this.speedDown = 0
this.speedUp = 0
this.status = 'uninitialized'
this.token = null
}
onRequestDownload() { getClient().then(client => {
if (this.status !== 'ready') return client.add(
this.status = 'requesting' this.infoHash,
{ announce: client.tracker.announce },
(torrent) => {
this.setState({ status: 'downloading' })
getClient().then(client => { const updateSpeed = () => {
client.add(this.infoHash, { announce: client.tracker.announce }, (torrent) => { this.setState({
this.setState({ status: 'downloading' }) speedUp: torrent.uploadSpeed,
speedDown: torrent.downloadSpeed,
peers: torrent.numPeers,
})
}
torrent.on('upload', updateSpeed)
torrent.on("download", updateSpeed);
setInterval(updateSpeed, SPEED_REFRESH_TIME);
const updateSpeed = () => { const file = torrent.files[0];
this.setState({ const stream = file.createReadStream();
speedUp: torrent.uploadSpeed, stream.on("data", (chunk) => {
speedDown: torrent.downloadSpeed, if (this.status !== 'downloading') {
peers: torrent.numPeers return
}) }
}
torrent.on('upload', updateSpeed) if (torrent.progress === 1) {
torrent.on('download', updateSpeed) this.setState({ status: 'done', progress: 1 })
setInterval(updateSpeed, SPEED_REFRESH_TIME) file.getBlobURL((err, blobURL) => {
if (err) {
const file = torrent.files[0] throw err
const stream = file.createReadStream() }
stream.on('data', (chunk) => { downloadBlobURL(this.fileName, blobURL)
if (this.status !== 'downloading') return });
} else {
if (torrent.progress === 1) { this.setState({ progress: torrent.progress })
this.setState({ status: 'done', progress: 1 }) }
file.getBlobURL((err, blobURL) => {
if (err) throw err
downloadBlobURL(this.fileName, blobURL)
}) })
} else { },
this.setState({ progress: torrent.progress }) )
} });
}
}) },
}) 'DownloadStore'
}) )
}
}, 'DownloadStore')

@ -1,20 +1,21 @@
import SupportActions from '../actions/SupportActions' import SupportActions from '../actions/SupportActions';
import alt from '../alt' import alt from '../alt';
export default alt.createStore(class ErrorStore { export default alt.createStore(
class ErrorStore {
constructor() {
this.bindActions(SupportActions)
constructor() { this.status = 404;
this.bindActions(SupportActions) this.message = "Not Found";
this.stack = null
}
this.status = 404 onNoSupport() {
this.message = 'Not Found' this.status = 400
this.stack = null this.message = "No WebRTC Support. Please use Chrome or Firefox.";
} this.stack = null
}
onNoSupport() { },
this.status = 400 'ErrorStore'
this.message = 'No WebRTC Support. Please use Chrome or Firefox.' )
this.stack = null
}
}, 'ErrorStore')

@ -1,20 +1,21 @@
import SupportActions from '../actions/SupportActions' import SupportActions from '../actions/SupportActions';
import alt from '../alt' import alt from '../alt';
export default alt.createStore(class SupportStore { export default alt.createStore(
class SupportStore {
constructor() {
this.bindActions(SupportActions)
this.isSupported = true;
this.isChrome = false;
}
constructor() { onNoSupport() {
this.bindActions(SupportActions) this.isSupported = false
this.isSupported = true }
this.isChrome = false
}
onNoSupport() { onIsChrome() {
this.isSupported = false this.isChrome = true
} }
},
onIsChrome() { 'SupportStore'
this.isChrome = true )
}
}, 'SupportStore')

@ -1,6 +1,6 @@
import UploadActions from "../actions/UploadActions"; import socket from 'filepizza-socket';
import alt from "../alt"; import UploadActions from '../actions/UploadActions';
import socket from "filepizza-socket"; import alt from '../alt';
import { getClient } from "../wt"; import { getClient } from "../wt";
const SPEED_REFRESH_TIME = 2000; const SPEED_REFRESH_TIME = 2000;
@ -22,7 +22,9 @@ export default alt.createStore(
} }
onUploadFile(file) { onUploadFile(file) {
if (this.status !== "ready") return; if (this.status !== "ready") {
return;
}
this.status = "processing"; this.status = "processing";
getClient().then(client => { getClient().then(client => {
@ -30,9 +32,9 @@ export default alt.createStore(
const updateSpeed = () => { const updateSpeed = () => {
this.setState({ this.setState({
speedUp: torrent.uploadSpeed, speedUp: torrent.uploadSpeed,
peers: torrent.numPeers peers: torrent.numPeers,
}); });
}; }
torrent.on("upload", updateSpeed); torrent.on("upload", updateSpeed);
torrent.on("download", updateSpeed); torrent.on("download", updateSpeed);
@ -44,7 +46,7 @@ export default alt.createStore(
fileName: file.name, fileName: file.name,
fileSize: file.size, fileSize: file.size,
fileType: file.type, fileType: file.type,
infoHash: torrent.magnetURI infoHash: torrent.magnetURI,
}, },
(res) => { (res) => {
this.setState({ this.setState({
@ -54,11 +56,11 @@ export default alt.createStore(
fileName: file.name, fileName: file.name,
fileSize: file.size, fileSize: file.size,
fileType: file.type, fileType: file.type,
infoHash: torrent.magnetURI infoHash: torrent.magnetURI,
}); });
} },
); );
}); })
}); });
} }
}, },

@ -96,5 +96,5 @@ export default [
"walnuts", "walnuts",
"watercress", "watercress",
"whitebait", "whitebait",
"zucchini" "zucchini",
] ]

@ -1,9 +1,11 @@
// Taken from StackOverflow // Taken from StackOverflow
// http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript // http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
export function formatSize(bytes) { export function formatSize(bytes) {
if (bytes === 0) return '0 Bytes' if (bytes === 0) {
return '0 Bytes';
}
const k = 1000 const k = 1000
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k)) ;const i = Math.floor(Math.log(bytes) / Math.log(k))
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i] return `${(bytes / Math.pow(k, i)).toPrecision(3)} ${sizes[i]}`;
} }

@ -1,12 +1,12 @@
import socket from 'filepizza-socket' import socket from 'filepizza-socket';
export function getClient() { export function getClient() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
socket.emit('trackerConfig', {}, (trackerConfig) => { socket.emit('trackerConfig', {}, (trackerConfig) => {
const client = new WebTorrent({ const client = new WebTorrent({
tracker: trackerConfig tracker: trackerConfig,
}) })
resolve(client) resolve(client)
}) });
}) })
} }

@ -20,6 +20,7 @@
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
"**/*.js",
"**/*.ts", "**/*.ts",
"**/*.tsx" "**/*.tsx"
], ],

@ -1,12 +1,11 @@
const nib = require("nib"); const nib = require("nib");
const webpack = require('webpack') const webpack = require('webpack')
;module.exports = {
module.exports = {
entry: "./src/client", entry: "./src/client",
target: "web", target: "web",
output: { output: {
filename: "dist/bundle.js" filename: "dist/bundle.js",
}, },
module: { module: {
@ -14,37 +13,39 @@ module.exports = {
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
loader: "babel-loader" loader: "babel-loader",
}, },
{ {
test: /\.json$/, test: /\.json$/,
loader: "json" loader: "json",
}, },
{ {
test: /\.styl$/, test: /\.styl$/,
loader: "style-loader!css-loader!stylus-loader" loader: "style-loader!css-loader!stylus-loader",
} },
] ],
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
'GA_ACCESS_TOKEN': JSON.stringify(process.env.GA_ACCESS_TOKEN), GA_ACCESS_TOKEN: JSON.stringify(process.env.GA_ACCESS_TOKEN),
} },
}) }),
], ],
node: { node: {
fs: "empty" fs: "empty",
}, },
stylus: { stylus: {
use: [nib()] use: [nib()],
}, },
rules: [{ rules: [
test: /react-google-analytics/, {
use: process.env.GA_ACCESS_TOKEN ? 'null-loader' : 'noop-loader' test: /react-google-analytics/,
}] use: process.env.GA_ACCESS_TOKEN ? 'null-loader' : 'noop-loader',
},
],
}; };

@ -1187,6 +1187,16 @@
"@typescript-eslint/typescript-estree" "2.34.0" "@typescript-eslint/typescript-estree" "2.34.0"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/parser@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.1.1.tgz#324b4b35e314075adbc92bd8330cf3ef0c88cf3e"
integrity sha512-NLIhmicpKGfJbdXyQBz9j48PA6hq6e+SDOoXy7Ak6bq1ebGqbgG+fR1UIDAuay6OjQdot69c/URu2uLlsP8GQQ==
dependencies:
"@typescript-eslint/scope-manager" "4.1.1"
"@typescript-eslint/types" "4.1.1"
"@typescript-eslint/typescript-estree" "4.1.1"
debug "^4.1.1"
"@typescript-eslint/scope-manager@4.1.1": "@typescript-eslint/scope-manager@4.1.1":
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.1.tgz#bdb8526e82435f32b4ccd9dd4cec01af97b48850" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.1.tgz#bdb8526e82435f32b4ccd9dd4cec01af97b48850"
@ -3271,6 +3281,13 @@ escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-prettier@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"
integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==
dependencies:
get-stdin "^6.0.0"
eslint-import-resolver-node@^0.3.3: eslint-import-resolver-node@^0.3.3:
version "0.3.4" version "0.3.4"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
@ -3901,6 +3918,11 @@ generate-object-property@^1.1.0:
dependencies: dependencies:
is-property "^1.0.0" is-property "^1.0.0"
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@^3.0.0: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"

Loading…
Cancel
Save