├── public
├── js
│ ├── sw.js
│ ├── html5shiv.min.js
│ ├── es5-sham.min.js
│ └── es5-shim.min.js
├── favicon.ico
├── img
│ ├── icon.png
│ ├── example.gif
│ └── logo.svg
├── favicon-16x16.png
├── favicon-32x32.png
├── mstile-70x70.png
├── mstile-144x144.png
├── mstile-150x150.png
├── mstile-310x150.png
├── mstile-310x310.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── browserconfig.xml
├── manifest.json
├── css
│ └── style.css
└── safari-pinned-tab.svg
├── .gitignore
├── entrypoint
├── views
├── index.pug
└── index.html
├── .babelrc
├── src
├── index.js
└── ui
│ ├── Error.js
│ ├── Site.js
│ └── Home.js
├── .eslintrc
├── Dockerfile
├── serverdev.js
├── webpack.config.js
├── webpack.production.config.js
├── LICENSE
├── package.json
├── server.js
└── README.md
/public/js/sw.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/img/icon.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/img/example.gif
--------------------------------------------------------------------------------
/public/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/mstile-70x70.png
--------------------------------------------------------------------------------
/public/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/mstile-144x144.png
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/mstile-310x150.png
--------------------------------------------------------------------------------
/public/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/mstile-310x310.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | public/bundle.js
2 | public/bundle.js.map
3 | node_modules
4 | npm-debug.log
5 | *.swp
6 | *.swo
7 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tannercollin/Notica/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | if [ "${1#-}" != "$1" ]; then
5 | set -- yarn start "$@"
6 | fi
7 |
8 | exec "$@"
9 |
--------------------------------------------------------------------------------
/views/index.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | script.
3 | var secureID = '!{secureID}';
4 | var title = '!{title}';
5 | var icon = '!{icon}';
6 | include index.html
7 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react"
5 | ],
6 | "env": {
7 | "production": {
8 | "plugins": [
9 | "transform-react-constant-elements",
10 | "transform-react-inline-elements"
11 | ]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #00aba9
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route, browserHistory } from 'react-router';
3 | import Site from './ui/Site';
4 |
5 | React.render((
6 |
7 |
8 |
9 | ), document.getElementById('root'));
10 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "icons": [
4 | {
5 | "src": "\/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image\/png"
8 | },
9 | {
10 | "src": "\/android-chrome-512x512.png",
11 | "sizes": "512x512",
12 | "type": "image\/png"
13 | }
14 | ],
15 | "theme_color": "#ffffff",
16 | "display": "standalone"
17 | }
18 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "jsx": true,
4 | "modules": true
5 | },
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "parser": "babel-eslint",
11 | "rules": {
12 | "quotes": [2, "single"],
13 | "strict": [2, "never"],
14 | "react/jsx-uses-react": 2,
15 | "react/jsx-uses-vars": 2,
16 | "react/react-in-jsx-scope": 2
17 | },
18 | "plugins": [
19 | "react"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:13-alpine
2 |
3 | COPY / /notica/
4 |
5 | WORKDIR /notica/
6 |
7 | RUN addgroup -S notica && \
8 | adduser -S notica -G notica && \
9 | apk add -U tzdata tini && \
10 | yarn install && \
11 | chown -R notica:notica /notica/ && \
12 | chmod +x /notica/entrypoint
13 |
14 | USER notica
15 |
16 | EXPOSE 3000
17 |
18 | ENTRYPOINT ["tini", "--", "/notica/entrypoint"]
19 |
20 | CMD ["yarn","start","--host","0.0.0.0","--port","3000"]
21 |
--------------------------------------------------------------------------------
/serverdev.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('./webpack.config');
4 |
5 | new WebpackDevServer(webpack(config), {
6 | contentBase: './public',
7 | hot: true,
8 | historyApiFallback: true,
9 | noInfo: true
10 | }).listen(3000, 'localhost', function (err, result) {
11 | if (err) {
12 | console.log(err);
13 | }
14 |
15 | console.log('Listening at localhost:3000');
16 | });
17 |
--------------------------------------------------------------------------------
/src/ui/Error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import React from 'react';
3 |
4 | export default class Error extends React.Component {
5 | render(){
6 | return (
7 |
8 |
9 |
10 |
Error
11 |
12 | Something went wrong so we stopped everything and went fishing.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'eval',
6 | entry: [
7 | 'webpack-dev-server/client?https://notica.us',
8 | 'webpack/hot/only-dev-server',
9 | './src/index'
10 | ],
11 | output: {
12 | path: path.join(__dirname, 'public'),
13 | filename: 'bundle.js',
14 | publicPath: ''
15 | },
16 | plugins: [
17 | new webpack.HotModuleReplacementPlugin(),
18 | new webpack.NoErrorsPlugin()
19 | ],
20 | watchOptions: {
21 | poll: true
22 | },
23 | module: {
24 | loaders: [{
25 | test: /\.js$/,
26 | loaders: ['react-hot', 'babel-loader'],
27 | include: path.join(__dirname, 'src')
28 | }]
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'cheap-module-source-map',
6 | entry: [
7 | './src/index'
8 | ],
9 | output: {
10 | path: path.join(__dirname, 'public'),
11 | filename: 'bundle.js',
12 | publicPath: ''
13 | },
14 | plugins: [
15 | new webpack.HotModuleReplacementPlugin(),
16 | new webpack.DefinePlugin({
17 | 'process.env': {
18 | 'NODE_ENV': JSON.stringify('production')
19 | }
20 | }),
21 | new webpack.optimize.UglifyJsPlugin({
22 | compress: {
23 | warnings: false
24 | }
25 | }),
26 | new webpack.NoErrorsPlugin()
27 | ],
28 | module: {
29 | loaders: [{
30 | test: /\.js$/,
31 | loaders: ['babel-loader'],
32 | include: path.join(__dirname, 'src')
33 | }]
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/public/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | .hero {
6 | background: #84DCCF;
7 | text-align: center;
8 | box-shadow: 0 0 7px #555;
9 | margin-bottom: 2.5rem;
10 | }
11 |
12 | .title {
13 | padding-top: 1rem;
14 | font-size: 5rem;
15 | }
16 | .title a {
17 | color: black;
18 | text-decoration: none;
19 | }
20 | .title .name {
21 | vertical-align: middle;
22 | }
23 | .title img {
24 | padding-right: 0.5rem;
25 | vertical-align: middle;
26 | width: 5rem;
27 | }
28 |
29 | .tagline {
30 | padding-bottom: 2.5rem;
31 | font-size: 2rem;
32 | }
33 |
34 | .container {
35 | font-size: 2rem;
36 | }
37 |
38 | .content {
39 | margin: auto;
40 | max-width: 800px;
41 | }
42 |
43 | .error {
44 | color: #cc0000;
45 | }
46 |
47 | .storSupport p {
48 | background: rgba(132, 220, 207, 0.2);
49 | padding: 1rem;
50 | }
51 |
52 | code {
53 | white-space: normal;
54 | }
55 |
56 | .smallcode {
57 | padding: 0;
58 | }
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Tanner Collin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Notica",
3 | "version": "1.2.0",
4 | "description": "Send browser notifications from your terminal. No installation. No registration.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/tannercollin/Notica"
8 | },
9 | "scripts": {
10 | "dev": "node serverdev.js",
11 | "build": "webpack --config ./webpack.production.config.js --profile --display-error-details --no-color",
12 | "start": "yarn run build && node server.js",
13 | "lint": "eslint src"
14 | },
15 | "author": "Tanner Collin (http://github.com/tannercollin)",
16 | "license": "MIT",
17 | "devDependencies": {
18 | "babel-core": "^6.21.0",
19 | "babel-eslint": "^3.1.9",
20 | "babel-loader": "^6.2.10",
21 | "babel-plugin-transform-react-constant-elements": "^6.9.1",
22 | "babel-plugin-transform-react-inline-elements": "^6.8.0",
23 | "babel-preset-es2015": "^6.18.0",
24 | "babel-preset-react": "^6.16.0",
25 | "eslint-plugin-react": "^2.3.0",
26 | "react-hot-loader": "^1.2.7",
27 | "webpack": "^1.9.6",
28 | "webpack-dev-server": ">=3.1.11"
29 | },
30 | "dependencies": {
31 | "base64-url": ">=2.0.0",
32 | "body-parser": "^1.15.2",
33 | "commander": "^2.9.0",
34 | "freezer-js": "^0.6.0",
35 | "moment": "^2.29.2",
36 | "pug": "^3.0.1",
37 | "qrcode.react": "^0.6.1",
38 | "react": "^0.14.0",
39 | "react-router": "^2.0.0",
40 | "shortid": "^2.2.6",
41 | "socket.io": "^2.4.0",
42 | "socket.io-client": "^1.7.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/ui/Site.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import React from 'react';
3 | import Home from './Home';
4 | import Error from './Error';
5 | import Shortid from 'shortid';
6 | import { Link, browserHistory } from 'react-router';
7 |
8 | export default class Site extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | page: null,
14 | id: '',
15 | storSupport: (typeof localStorage !== 'undefined')
16 | }
17 | }
18 |
19 | componentWillMount() {
20 | this.setPage();
21 | }
22 |
23 | componentDidUpdate(prevProps) {
24 | let oldUrl = prevProps.params.splat;
25 | let newUrl = this.props.params.splat;
26 | if (newUrl !== oldUrl) this.setPage();
27 | }
28 |
29 | setId(url) {
30 | let id = Shortid.generate();
31 |
32 | try {
33 | id = secureID;
34 | } catch (err) {
35 | console.log('SecureID not found. Using Shortid instead.');
36 | }
37 |
38 | if (this.state.storSupport) {
39 | if (localStorage.getItem('id')) {
40 | this.state.id = url || localStorage.getItem('id');
41 | } else {
42 | this.state.id = url || id;
43 | }
44 | localStorage.setItem('id', this.state.id);
45 | } else {
46 | this.state.id = url || id;
47 | }
48 | }
49 |
50 | setPage() {
51 | let url = this.props.params.splat;
52 | let queryId = Object.keys(this.props.location.query)[0] || '';
53 |
54 | if (url == 'clear') {
55 | localStorage.clear();
56 | url = '';
57 | queryId = '';
58 | }
59 |
60 | if (url == '' && queryId == '') {
61 | this.setId();
62 | browserHistory.push('/?' + this.state.id);
63 | this.state.page = ;
64 | }
65 | else if (Shortid.isValid(url) || Shortid.isValid(queryId)) {
66 | let id = url || queryId;
67 | this.setId(id);
68 | this.state.page = ;
69 | }
70 | else {
71 | this.state.page = ;
72 | }
73 | }
74 |
75 | render(){
76 | return (
77 |
78 |
79 |
80 |
81 |
82 |
Notica
83 |
84 |
85 |
86 | Send browser notifications from your terminal. No installation. No registration.
87 |
88 |
89 | {this.state.page}
90 |
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/public/js/html5shiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML=" ",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}("undefined"!=typeof window?window:this,document);
--------------------------------------------------------------------------------
/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Notica - Notifications from Your Terminal
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 | Send browser notifications from your terminal. No installation. No registration.
53 |
54 |
55 |
56 |
57 |
58 |
Javascript Disabled
59 |
60 | Uh oh - It looks like Javascript is disabled or not supported in your browser.
61 | This is bad news for Notica, because we need Javascript to run!
62 |
63 |
64 | Without it, we are unable to display browser notifications or create a Websocket to our server :(
65 |
66 |
67 | Please enable Javascript to use Notica. The site looks much prettier with it enabled!
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const pug = require('pug');
4 | const bodyParser = require('body-parser');
5 | const moment = require('moment');
6 | const crypto = require('crypto');
7 | const base64url = require('base64-url');
8 |
9 | const pjson = require('./package.json');
10 | var program = require('commander');
11 |
12 | const app = express();
13 |
14 | var options = {
15 | port: 3000,
16 | host: '127.0.0.1',
17 | url: 'https://notica.us',
18 | title: 'Notification from Notica',
19 | icon: 'img/icon.png',
20 | };
21 |
22 | program.version(pjson.version)
23 | .option('-p, --port ', 'Host port (3000)', '3000')
24 | .option('-H, --host ', 'Host IP (127.0.0.1)', '127.0.0.1')
25 | .option('-U, --url ', 'Website URL (https://notica.us)', 'https://notica.us')
26 | .option('-t, --title ', 'Custom title (\'Notification from Notica\')', 'Notification from Notica')
27 | .option('-i, --icon ', 'Custom icon (img/icon.png)', 'img/icon.png');
28 |
29 | program.on('--help', function() {
30 | console.log('');
31 | console.log(' Example:');
32 | console.log('');
33 | console.log(' $ npm start -- -p 80 -t \'My cool Title\'');
34 | console.log('');
35 | });
36 |
37 | program.parse(process.argv);
38 |
39 | Object.keys(options).forEach(function(key) {
40 | options[key] = program[key] || options[key];
41 | });
42 |
43 | const host = options.host;
44 | const port = options.port;
45 | const url = options.url;
46 | const title = options.title;
47 | const icon = options.icon;
48 |
49 | if (port == 80 || port == 443) {
50 | console.log('WARNING: For security, you should run Notica behind a reverse proxy. See README.');
51 | }
52 |
53 | app.use(bodyParser.urlencoded({ extended: false }));
54 | app.set('view engine', 'pug')
55 |
56 | function log(message) {
57 | console.log(moment().format() + ': ' + message);
58 | }
59 |
60 | function generateID() {
61 | const bytes = crypto.randomBytes(30);
62 | const string = base64url.encode(bytes);
63 | return string.substring(0, 6);
64 | }
65 |
66 | app.use('/', express.static(path.join(__dirname, 'public')));
67 | app.get('/*', (req, res) => {
68 | res.render('index', { secureID: generateID(), title: title, icon: icon })
69 | });
70 |
71 | app.post('*', (req, res) => {
72 | let id = req.path.substring(1, 20); // maintain backwards compat.
73 | id = id || Object.keys(req.query)[0] || '';
74 | let data = Object.keys(req.body)[0];
75 |
76 | if (data && data.substring(0,2) === 'd:') {
77 | if (io.sockets.adapter.rooms[id]) {
78 | let message = data.substring(2);
79 |
80 | log('[NOTICA] Message sent to ' + id + ': ' + message);
81 |
82 | io.in(id).emit('message', message);
83 |
84 | res.end();
85 | } else {
86 | log('No one in room to send data to: ' + id);
87 | res.status(404).send('No devices have that Notica ID open. Please open this URL: '
88 | + url + '/?' + id + '\n');
89 | }
90 | } else {
91 | log('WTF')
92 | log('Ignoring bad POST data to: ' + id);
93 | res.status(400).send('Bad POST data. Expecting prefix of "d:".\n');
94 | }
95 | });
96 |
97 | const server = app.listen(port, host, (err) => {
98 | if (err) {
99 | log('[ERROR] Server error: ' + err);
100 | return;
101 | }
102 | console.info('==> Listening on port %s. Open up http://%s:%s/ in your browser.', port, host, port);
103 | });
104 |
105 | const io = require('socket.io').listen(server);
106 |
107 | io.on('connection', (socket) => {
108 | var address = socket.handshake.address;
109 | log('New connection from ' + address);
110 | socket.on('room', (room) => {
111 | log('New connection joining room: ' + room);
112 | socket.join(room);
113 | });
114 | });
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notica
2 | Send browser notifications from your terminal. No installation. No registration.
3 |
4 | https://notica.us/
5 |
6 | ## Usage
7 |
8 | Notica is a Bash function / alias that sends a notification to a tab in your browser when it's ran:
9 |
10 | ```
11 | $ long-running-command; notica Finished!
12 | ```
13 |
14 | This will wait until the first command completes before running Notica.
15 | That way you can go do other things while your long task runs.
16 | Then you will recieve a notification on any devices that have the Notica website open.
17 |
18 | 
19 |
20 | ## Setup
21 |
22 | Please follow the instructions on the Notica home page since they are generated specific to you:
23 |
24 | https://notica.us/
25 |
26 | ## Source Code
27 |
28 | ### Self-hosting
29 |
30 | Hosting Notica on your own server is extremely easy.
31 | Clone this repository, change all notica.us URLs to your own domain, and then run `yarn install && yarn start`.
32 |
33 | #### Usage
34 |
35 | ```text
36 | Usage: yarn start [options]
37 |
38 | Options:
39 |
40 | -V, --version output the version number
41 | -p, --port Host port (3000)
42 | -H, --host Host IP (127.0.0.1)
43 | -U, --url Website URL (https://notica.us)
44 | -t, --title Custom title ('Notification from Notica')
45 | -i, --icon Custom icon (img/icon.png)
46 | -h, --help output usage information
47 |
48 | Example:
49 |
50 | $ yarn start -p 1234 -t 'My cool Title'
51 | ```
52 |
53 | #### Reverse Proxy
54 |
55 | For security, it is recommended to run Notica behind a reverse proxy as a separate non-privileged user.
56 |
57 | Here's a sample nginx reverse proxy config:
58 |
59 | ```
60 | server {
61 | listen 80;
62 | listen [::]:80;
63 |
64 | root /var/www/html;
65 | index index.html index.htm;
66 |
67 | server_name notica.us;
68 |
69 | location / {
70 | proxy_pass http://127.0.0.1:3000/;
71 | proxy_set_header Host $http_host;
72 | proxy_set_header X-Real-IP $remote_addr;
73 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
74 | proxy_set_header X-Forwarded-Proto $scheme;
75 | }
76 | }
77 | ```
78 |
79 | Add SSL with certbot:
80 |
81 | ```
82 | $ sudo apt install certbot python-certbot-nginx
83 | $ sudo certbot --nginx
84 | ```
85 |
86 | Or Apache:
87 |
88 | ```
89 |
90 | ServerName notica.us
91 |
92 | ProxyPass / http://127.0.0.1:3000/
93 | ProxyPassReverse / http://127.0.0.1:3000/
94 | ProxyPreserveHost On
95 |
96 | ErrorLog ${APACHE_LOG_DIR}/notica-error.log
97 | CustomLog ${APACHE_LOG_DIR}/notica-access.log combined
98 |
99 | ```
100 |
101 | Apache SSL is left as an exercise for the reader :)
102 |
103 | #### Process Control
104 |
105 | I recommend using `supervisor` to auto-start and keep Notica running.
106 |
107 | ```
108 | $ sudo apt install supervisor
109 | $ sudo adduser --disabled-login --gecos '' --shell /bin/false notica
110 | $ sudo chown -R notica:notica /opt/Notica
111 | ```
112 |
113 | Add to `/etc/supervisor/supervisord.conf` or its own file:
114 |
115 | ```
116 | [program:notica]
117 | user=notica
118 | directory=/opt/Notica
119 | command=node server.js
120 | autorestart=true
121 | stopasgroup=true
122 | killasgroup=true
123 | stderr_logfile=/var/log/notica.log
124 | stderr_logfile_maxbytes=10MB
125 | stdout_logfile=/var/log/notica.log
126 | stdout_logfile_maxbytes=10MB
127 | ```
128 |
129 | ### Self-hosting with Docker
130 |
131 | #### Build
132 |
133 | ```
134 | docker build -t notica .
135 | ```
136 |
137 | #### Run
138 |
139 | ```
140 | docker run --rm -it -p 3000:3000 notica
141 | ```
142 |
143 | #### With Traefik Reverse Proxy
144 |
145 | ```
146 | docker run -d \
147 | --name notica \
148 | --restart unless-stopped \
149 | --label "traefik.enable=true" \
150 | --label "traefik.frontend.rule=Host:notica.example.com" \
151 | --label "traefik.port=3000" \
152 | --network traefik-network \
153 | -e TZ=Europe/London \
154 | notica
155 | ```
156 |
157 | ## License
158 |
159 | This program is free and open-source software licensed under the MIT License. Please see the `LICENSE` file for details.
160 |
161 | That means you have the right to study, change, and distribute the software and source code to anyone and for any purpose. You deserve these rights. Please take advantage of them because I like pull requests and would love to see this code put to use.
162 |
163 | ## Acknowledgements
164 |
165 | Thanks to welbert, damc-dev, scribblemaniac, and lukasmrtvy for their contributions.
166 |
--------------------------------------------------------------------------------
/public/js/es5-sham.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * https://github.com/es-shims/es5-shim
3 | * @license es5-shim Copyright 2009-2014 by contributors, MIT License
4 | * see https://github.com/es-shims/es5-shim/blob/v4.1.0/LICENSE
5 | */
6 | (function(e,t){"use strict";if(typeof define==="function"&&define.amd){define(t)}else if(typeof exports==="object"){module.exports=t()}else{e.returnExports=t()}})(this,function(){var e=Function.prototype.call;var t=Object.prototype;var r=e.bind(t.hasOwnProperty);var n;var o;var c;var i;var f=r(t,"__defineGetter__");if(f){n=e.bind(t.__defineGetter__);o=e.bind(t.__defineSetter__);c=e.bind(t.__lookupGetter__);i=e.bind(t.__lookupSetter__)}if(!Object.getPrototypeOf){Object.getPrototypeOf=function E(e){var r=e.__proto__;if(r||r===null){return r}else if(e.constructor){return e.constructor.prototype}else{return t}}}function l(e){try{e.sentinel=0;return Object.getOwnPropertyDescriptor(e,"sentinel").value===0}catch(t){}}if(Object.defineProperty){var u=l({});var a=typeof document==="undefined"||l(document.createElement("div"));if(!a||!u){var p=Object.getOwnPropertyDescriptor}}if(!Object.getOwnPropertyDescriptor||p){var b="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function g(e,n){if(typeof e!=="object"&&typeof e!=="function"||e===null){throw new TypeError(b+e)}if(p){try{return p.call(Object,e,n)}catch(o){}}var l;if(!r(e,n)){return l}l={enumerable:true,configurable:true};if(f){var u=e.__proto__;var a=e!==t;if(a){e.__proto__=t}var s=c(e,n);var O=i(e,n);if(a){e.__proto__=u}if(s||O){if(s){l.get=s}if(O){l.set=O}return l}}l.value=e[n];l.writable=true;return l}}if(!Object.getOwnPropertyNames){Object.getOwnPropertyNames=function T(e){return Object.keys(e)}}if(!Object.create){var s;var O=!({__proto__:null}instanceof Object);if(O||typeof document==="undefined"){s=function(){return{__proto__:null}}}else{s=function(){var e=document.createElement("iframe");var t=document.body||document.documentElement;e.style.display="none";t.appendChild(e);e.src="javascript:";var r=e.contentWindow.Object.prototype;t.removeChild(e);e=null;delete r.constructor;delete r.hasOwnProperty;delete r.propertyIsEnumerable;delete r.isPrototypeOf;delete r.toLocaleString;delete r.toString;delete r.valueOf;r.__proto__=null;function n(){}n.prototype=r;s=function(){return new n};return new n}}Object.create=function x(e,t){var r;function n(){}if(e===null){r=s()}else{if(typeof e!=="object"&&typeof e!=="function"){throw new TypeError("Object prototype may only be an Object or null")}n.prototype=e;r=new n;r.__proto__=e}if(t!==void 0){Object.defineProperties(r,t)}return r}}function j(e){try{Object.defineProperty(e,"sentinel",{});return"sentinel"in e}catch(t){}}if(Object.defineProperty){var d=j({});var y=typeof document==="undefined"||j(document.createElement("div"));if(!d||!y){var _=Object.defineProperty,v=Object.defineProperties}}if(!Object.defineProperty||_){var w="Property description must be an object: ";var P="Object.defineProperty called on non-object: ";var h="getters & setters can not be defined on this javascript engine";Object.defineProperty=function z(e,r,l){if(typeof e!=="object"&&typeof e!=="function"||e===null){throw new TypeError(P+e)}if(typeof l!=="object"&&typeof l!=="function"||l===null){throw new TypeError(w+l)}if(_){try{return _.call(Object,e,r,l)}catch(u){}}if("value"in l){if(f&&(c(e,r)||i(e,r))){var a=e.__proto__;e.__proto__=t;delete e[r];e[r]=l.value;e.__proto__=a}else{e[r]=l.value}}else{if(!f){throw new TypeError(h)}if("get"in l){n(e,r,l.get)}if("set"in l){o(e,r,l.set)}}return e}}if(!Object.defineProperties||v){Object.defineProperties=function S(e,t){if(v){try{return v.call(Object,e,t)}catch(n){}}for(var o in t){if(r(t,o)&&o!=="__proto__"){Object.defineProperty(e,o,t[o])}}return e}}if(!Object.seal){Object.seal=function D(e){if(Object(e)!==e){throw new TypeError("Object.seal can only be called on Objects.")}return e}}if(!Object.freeze){Object.freeze=function F(e){if(Object(e)!==e){throw new TypeError("Object.freeze can only be called on Objects.")}return e}}try{Object.freeze(function(){})}catch(m){Object.freeze=function k(e){return function t(r){if(typeof r==="function"){return r}else{return e(r)}}}(Object.freeze)}if(!Object.preventExtensions){Object.preventExtensions=function G(e){if(Object(e)!==e){throw new TypeError("Object.preventExtensions can only be called on Objects.")}return e}}if(!Object.isSealed){Object.isSealed=function C(e){if(Object(e)!==e){throw new TypeError("Object.isSealed can only be called on Objects.")}return false}}if(!Object.isFrozen){Object.isFrozen=function N(e){if(Object(e)!==e){throw new TypeError("Object.isFrozen can only be called on Objects.")}return false}}if(!Object.isExtensible){Object.isExtensible=function I(e){if(Object(e)!==e){throw new TypeError("Object.isExtensible can only be called on Objects.")}var t="";while(r(e,t)){t+="?"}e[t]=true;var n=r(e,t);delete e[t];return n}}});
7 | //# sourceMappingURL=es5-sham.map
--------------------------------------------------------------------------------
/public/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/ui/Home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import React from 'react';
3 | import io from 'socket.io-client';
4 | import QRCode from 'qrcode.react';
5 |
6 | export default class Home extends React.Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | supported: false,
12 | registration: null,
13 | haveperm: false,
14 | connected: false,
15 | socket: io.connect(),
16 | storSupport: (typeof localStorage !== 'undefined'),
17 | alerts: new Array(),
18 | }
19 | }
20 |
21 | componentDidMount() {
22 | this.checksupport();
23 | this.checkperm();
24 | this.getAlerts();
25 | this.connect();
26 | }
27 |
28 | componentWillUnmount() {
29 | this.setState({socket: this.state.socket.removeAllListeners()});
30 | }
31 |
32 | connect() {
33 | let socket = this.state.socket;
34 |
35 | let room = this.props.id;
36 |
37 | socket.on('connect', () => {
38 | socket.emit('room', room);
39 | this.setState({connected: true});
40 |
41 | socket.on('disconnect', () => {
42 | this.setState({connected: false});
43 | });
44 | });
45 |
46 | socket.on('message', (data) => {
47 | this.sendNotification(data);
48 | this.addAlert(data);
49 | });
50 | }
51 |
52 | addAlert(data) {
53 | if (this.state.storSupport) {
54 | let alerts = this.state.alerts;
55 | alerts.unshift(data);
56 | localStorage.setItem('alerts', JSON.stringify(alerts));
57 | this.getAlerts();
58 | }
59 | }
60 |
61 | getAlerts() {
62 | if (this.state.storSupport) {
63 | let alerts = new Array();
64 | let alertsJson = localStorage.getItem('alerts');
65 | if (alertsJson != null) {
66 | alerts = JSON.parse(alertsJson);
67 | }
68 | this.setState({alerts: alerts});
69 | }
70 | }
71 |
72 | clearAlerts() {
73 | if (this.state.storSupport) {
74 | if (localStorage.getItem('alerts') != null) {
75 | localStorage.removeItem('alerts');
76 | }
77 | this.getAlerts();
78 | }
79 | }
80 |
81 | sendNotification(data) {
82 | let message = data || 'Received a notification!';
83 |
84 | let options = {
85 | body: title,
86 | icon: icon,
87 | iconUrl: icon,
88 | vibrate: [200, 100, 200]
89 | };
90 |
91 | if (this.state.registration) {
92 | this.state.registration.showNotification(message, options);
93 | } else {
94 | new Notification(message, options);
95 | }
96 | }
97 |
98 | checksupport() {
99 | let supported = ('Notification' in window);
100 | this.setState({supported: supported});
101 | }
102 |
103 | askperm() {
104 | if (this.state.supported) {
105 | Notification.requestPermission(permission => {
106 | this.checkperm();
107 |
108 | try {
109 | navigator.serviceWorker.register('/js/sw.js').then((reg) => {
110 | this.setState({registration: reg});
111 | });
112 | } catch (e) { // If we are on a browser without serviceWorker
113 | this.setState({registration: false});
114 | }
115 | });
116 | }
117 | }
118 |
119 | checkperm() {
120 | if (Notification.permission === 'granted') {
121 | this.setState({haveperm: true});
122 | }
123 | else {
124 | this.setState({haveperm: false});
125 | }
126 | }
127 |
128 | render(){
129 | const id = this.props.id;
130 | const storSupport = this.props.storSupport;
131 | const supported = this.state.supported;
132 | const haveperm = this.state.haveperm;
133 | const connected = this.state.connected;
134 |
135 | const port = location.port ? ':' + location.port : '';
136 | const url = location.protocol + '//' + location.hostname + '/?';
137 | const alerts = this.state.alerts.map((value,index) =>
138 | {value}
139 | );
140 |
141 | return (
142 |
143 |
144 |
145 |
Status
146 |
147 | { supported ||
148 | This browser does not support desktop notifications.
149 |
}
150 | { !haveperm && supported &&
}
160 | { !connected && supported &&
161 |
162 | Unable to connect to the Notica server.
163 |
164 | Attempting to reconnect...
165 |
166 |
}
167 | { haveperm && connected && supported &&
168 | This page is monitoring for notifications.
169 |
}
170 |
171 |
172 | {!!alerts.length &&
173 |
174 |
Previous Notifications
175 |
176 | { !storSupport &&
177 | This browser does not support local storage so it is unable to save notifications.
178 |
}
179 | { storSupport &&
}
187 |
188 |
}
189 |
190 |
191 |
Usage
192 |
193 | Notica sends a notification to a tab in your browser when ran. It works over SSH and to your phone.
194 |
195 |
196 |
197 |
198 |
199 |
$ long-running-command; notica Finished!
200 |
201 | This will wait until the first command completes before running Notica. You can go do things while your long task runs, then you will recieve a notification on any pages that have this website open.
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
Quick Setup
211 |
212 | Run this command:
213 |
214 | $ echo 'notica() { curl --data "d:$*" "{url + id}" ; }' >> ~/.bashrc && source ~/.bashrc
215 |
216 |
217 |
Now open this page on any devices you want to receive the notifications on: {url + id}
218 |
219 |
Setup
220 |
Curl is required to use Notica.
221 |
222 | Add this line to your .bashrc file:
223 |
224 | notica() { curl --data "d:$*" "{url + id}" ; }
225 |
226 |
227 |
228 | Source your .bashrc file to apply the changes:
229 | $ source .bashrc
230 |
231 |
232 | All done! Now open this page on any devices you want to receive the notifications on: {url + id}
233 |
234 | { storSupport &&
235 | Notica uses Local Storage to keep track of your unique ID. If you would like to generate a new random ID, click here .
236 |
}
237 |
238 |
239 |
240 |
241 |
Examples
242 |
Here are some different ways to use Notica:
243 |
244 | Just run it from your terminal:
245 |
246 | $ notica
247 |
248 |
249 |
250 | Add a custom message:
251 |
252 | $ notica Hello world!
253 |
254 |
255 |
256 | Get an alert when a command finishes:
257 |
258 | $ sudo apt-get update; notica Done!
259 |
260 |
261 |
262 | Get an alert when a command succeeds:
263 |
264 | $ make all && notica Success!
265 |
266 |
267 |
268 | Get an alert when a command fails:
269 |
270 | $ make all || notica Failed!
271 |
272 |
273 |
274 |
275 |
Tips
276 |
Bookmark this page! It is unique to the function in your .bashrc file.
277 | Notifications will be sent to all open pages with the same ID code in the URL.
278 |
Lose the link to this page? Just run Notica again:
279 |
280 | $ notica
281 | {url + id}
282 |
283 |
284 |
285 | Use quotes on messages with special characters:
286 |
287 | $ notica "This is awesome :)"
288 |
289 |
290 |
291 | Open this page on your phone:
292 |
293 |
294 |
295 |
296 |
297 |
298 |
About
299 |
300 | Notica was written by Tanner Collin after he got tired of checking if his commands were done running.
301 |
302 |
303 | Notica is free and open-source software: https://github.com/tannercollin/Notica
304 |
305 |
306 | Thanks to exdevlin for thinking of the name. Thanks to all the devs behind Node.js, React, webpack, and socket.io.
307 |
308 |
309 |
310 |
311 | );
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/public/js/es5-shim.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * https://github.com/es-shims/es5-shim
3 | * @license es5-shim Copyright 2009-2014 by contributors, MIT License
4 | * see https://github.com/es-shims/es5-shim/blob/v4.1.0/LICENSE
5 | */
6 | (function(t,e){"use strict";if(typeof define==="function"&&define.amd){define(e)}else if(typeof exports==="object"){module.exports=e()}else{t.returnExports=e()}})(this,function(){var t=Array.prototype;var e=Object.prototype;var r=Function.prototype;var n=String.prototype;var i=Number.prototype;var a=t.slice;var o=t.splice;var u=t.push;var l=t.unshift;var f=r.call;var s=e.toString;var c=Array.isArray||function ye(t){return s.call(t)==="[object Array]"};var p=typeof Symbol==="function"&&typeof Symbol.toStringTag==="symbol";var h;var v=Function.prototype.toString,g=function de(t){try{v.call(t);return true}catch(e){return false}},y="[object Function]",d="[object GeneratorFunction]";h=function me(t){if(typeof t!=="function"){return false}if(p){return g(t)}var e=s.call(t);return e===y||e===d};var m;var b=RegExp.prototype.exec,w=function be(t){try{b.call(t);return true}catch(e){return false}},T="[object RegExp]";m=function we(t){if(typeof t!=="object"){return false}return p?w(t):s.call(t)===T};var x;var O=String.prototype.valueOf,j=function Te(t){try{O.call(t);return true}catch(e){return false}},S="[object String]";x=function xe(t){if(typeof t==="string"){return true}if(typeof t!=="object"){return false}return p?j(t):s.call(t)===S};var E=function Oe(t){var e=s.call(t);var r=e==="[object Arguments]";if(!r){r=!c(t)&&t!==null&&typeof t==="object"&&typeof t.length==="number"&&t.length>=0&&h(t.callee)}return r};var N=function(t){var e=Object.defineProperty&&function(){try{Object.defineProperty({},"x",{});return true}catch(t){return false}}();var r;if(e){r=function(t,e,r,n){if(!n&&e in t){return}Object.defineProperty(t,e,{configurable:true,enumerable:false,writable:true,value:r})}}else{r=function(t,e,r,n){if(!n&&e in t){return}t[e]=r}}return function n(e,i,a){for(var o in i){if(t.call(i,o)){r(e,o,i[o],a)}}}}(e.hasOwnProperty);function I(t){var e=typeof t;return t===null||e==="undefined"||e==="boolean"||e==="number"||e==="string"}var D={ToInteger:function je(t){var e=+t;if(e!==e){e=0}else if(e!==0&&e!==1/0&&e!==-(1/0)){e=(e>0||-1)*Math.floor(Math.abs(e))}return e},ToPrimitive:function Se(t){var e,r,n;if(I(t)){return t}r=t.valueOf;if(h(r)){e=r.call(t);if(I(e)){return e}}n=t.toString;if(h(n)){e=n.call(t);if(I(e)){return e}}throw new TypeError},ToObject:function(t){if(t==null){throw new TypeError("can't convert "+t+" to object")}return Object(t)},ToUint32:function Ee(t){return t>>>0}};var M=function Ne(){};N(r,{bind:function Ie(t){var e=this;if(!h(e)){throw new TypeError("Function.prototype.bind called on incompatible "+e)}var r=a.call(arguments,1);var n;var i=function(){if(this instanceof n){var i=e.apply(this,r.concat(a.call(arguments)));if(Object(i)===i){return i}return this}else{return e.apply(t,r.concat(a.call(arguments)))}};var o=Math.max(0,e.length-r.length);var u=[];for(var l=0;l0&&typeof e!=="number"){r=a.call(arguments);if(r.length<2){r.push(this.length-t)}else{r[1]=D.ToInteger(e)}}return o.apply(this,r)}},!U);var k=[].unshift(0)!==1;N(t,{unshift:function(){l.apply(this,arguments);return this.length}},k);N(Array,{isArray:c});var A=Object("a");var C=A[0]!=="a"||!(0 in A);var P=function Fe(t){var e=true;var r=true;if(t){t.call("foo",function(t,r,n){if(typeof n!=="object"){e=false}});t.call([1],function(){"use strict";r=typeof this==="string"},"x")}return!!t&&e&&r};N(t,{forEach:function Re(t){var e=D.ToObject(this),r=C&&x(this)?this.split(""):e,n=arguments[1],i=-1,a=r.length>>>0;if(!h(t)){throw new TypeError}while(++i>>0,i=Array(n),a=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var o=0;o>>0,i=[],a,o=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var u=0;u>>0,i=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var a=0;a>>0,i=arguments[1];if(!h(t)){throw new TypeError(t+" is not a function")}for(var a=0;a>>0;if(!h(t)){throw new TypeError(t+" is not a function")}if(!n&&arguments.length===1){throw new TypeError("reduce of empty array with no initial value")}var i=0;var a;if(arguments.length>=2){a=arguments[1]}else{do{if(i in r){a=r[i++];break}if(++i>=n){throw new TypeError("reduce of empty array with no initial value")}}while(true)}for(;i>>0;if(!h(t)){throw new TypeError(t+" is not a function")}if(!n&&arguments.length===1){throw new TypeError("reduceRight of empty array with no initial value")}var i,a=n-1;if(arguments.length>=2){i=arguments[1]}else{do{if(a in r){i=r[a--];break}if(--a<0){throw new TypeError("reduceRight of empty array with no initial value")}}while(true)}if(a<0){return i}do{if(a in r){i=t.call(void 0,i,r[a],a,e)}}while(a--);return i}},!J);var z=Array.prototype.indexOf&&[0,1].indexOf(1,2)!==-1;N(t,{indexOf:function Je(t){var e=C&&x(this)?this.split(""):D.ToObject(this),r=e.length>>>0;if(!r){return-1}var n=0;if(arguments.length>1){n=D.ToInteger(arguments[1])}n=n>=0?n:Math.max(0,r+n);for(;n>>0;if(!r){return-1}var n=r-1;if(arguments.length>1){n=Math.min(n,D.ToInteger(arguments[1]))}n=n>=0?n:r-Math.abs(n);for(;n>=0;n--){if(n in e&&t===e[n]){return n}}return-1}},$);var B=!{toString:null}.propertyIsEnumerable("toString"),G=function(){}.propertyIsEnumerable("prototype"),H=!F("x","0"),L=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],X=L.length;N(Object,{keys:function $e(t){var e=h(t),r=E(t),n=t!==null&&typeof t==="object",i=n&&x(t);if(!n&&!e&&!r){throw new TypeError("Object.keys called on a non-object")}var a=[];var o=G&&e;if(i&&H||r){for(var u=0;u9999?"+":"")+("00000"+Math.abs(n)).slice(0<=n&&n<=9999?-4:-6);e=t.length;while(e--){r=t[e];if(r<10){t[e]="0"+r}}return n+"-"+t.slice(0,2).join("-")+"T"+t.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"}},V);var W=false;try{W=Date.prototype.toJSON&&new Date(NaN).toJSON()===null&&new Date(K).toJSON().indexOf(Q)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(_){}if(!W){Date.prototype.toJSON=function He(t){var e=Object(this),r=D.ToPrimitive(e),n;if(typeof r==="number"&&!isFinite(r)){return null}n=e.toISOString;if(typeof n!=="function"){throw new TypeError("toISOString property is not callable")}return n.call(e)}}var te=Date.parse("+033658-09-27T01:46:40.000Z")===1e15;var ee=!isNaN(Date.parse("2012-04-04T24:00:00.500Z"))||!isNaN(Date.parse("2012-11-31T23:59:59.000Z"));var re=isNaN(Date.parse("2000-01-01T00:00:00.000Z"));if(!Date.parse||re||ee||!te){Date=function(t){function e(r,n,i,a,o,u,l){var f=arguments.length;if(this instanceof t){var s=f===1&&String(r)===r?new t(e.parse(r)):f>=7?new t(r,n,i,a,o,u,l):f>=6?new t(r,n,i,a,o,u):f>=5?new t(r,n,i,a,o):f>=4?new t(r,n,i,a):f>=3?new t(r,n,i):f>=2?new t(r,n):f>=1?new t(r):new t;s.constructor=e;return s}return t.apply(this,arguments)}var r=new RegExp("^"+"(\\d{4}|[+-]\\d{6})"+"(?:-(\\d{2})"+"(?:-(\\d{2})"+"(?:"+"T(\\d{2})"+":(\\d{2})"+"(?:"+":(\\d{2})"+"(?:(\\.\\d{1,}))?"+")?"+"("+"Z|"+"(?:"+"([-+])"+"(\\d{2})"+":(\\d{2})"+")"+")?)?)?)?"+"$");var n=[0,31,59,90,120,151,181,212,243,273,304,334,365];function i(t,e){var r=e>1?1:0;return n[e]+Math.floor((t-1969+r)/4)-Math.floor((t-1901+r)/100)+Math.floor((t-1601+r)/400)+365*(t-1970)}function a(e){return Number(new t(1970,0,1,0,0,0,e))}for(var o in t){e[o]=t[o]}e.now=t.now;e.UTC=t.UTC;e.prototype=t.prototype;e.prototype.constructor=e;e.parse=function u(e){var n=r.exec(e);if(n){var o=Number(n[1]),u=Number(n[2]||1)-1,l=Number(n[3]||1)-1,f=Number(n[4]||0),s=Number(n[5]||0),c=Number(n[6]||0),p=Math.floor(Number(n[7]||0)*1e3),h=Boolean(n[4]&&!n[8]),v=n[9]==="-"?1:-1,g=Number(n[10]||0),y=Number(n[11]||0),d;if(f<(s>0||c>0||p>0?24:25)&&s<60&&c<60&&p<1e3&&u>-1&&u<12&&g<24&&y<60&&l>-1&&l=0){r+=ie.data[e];ie.data[e]=Math.floor(r/t);r=r%t*ie.base}},numToString:function qe(){var t=ie.size;var e="";while(--t>=0){if(e!==""||t===0||ie.data[t]!==0){var r=String(ie.data[t]);if(e===""){e=r}else{e+="0000000".slice(0,7-r.length)+r}}}return e},pow:function Ke(t,e,r){return e===0?r:e%2===1?Ke(t,e-1,r*t):Ke(t*t,e/2,r)},log:function Qe(t){var e=0;while(t>=4096){e+=12;t/=4096}while(t>=2){e+=1;t/=2}return e}};N(i,{toFixed:function Ve(t){var e,r,n,i,a,o,u,l;e=Number(t);e=e!==e?0:Math.floor(e);if(e<0||e>20){throw new RangeError("Number.toFixed called with invalid number of decimals")}r=Number(this);if(r!==r){return"NaN"}if(r<=-1e21||r>=1e21){return String(r)}n="";if(r<0){n="-";r=-r}i="0";if(r>1e-21){a=ie.log(r*ie.pow(2,69,1))-69;o=a<0?r*ie.pow(2,-a,1):r/ie.pow(2,a,1);o*=4503599627370496;a=52-a;if(a>0){ie.multiply(0,o);u=e;while(u>=7){ie.multiply(1e7,0);u-=7}ie.multiply(ie.pow(10,u,1),0);u=a-1;while(u>=23){ie.divide(1<<23);u-=23}ie.divide(1<0){l=i.length;if(l<=e){i=n+"0.0000000000000000000".slice(0,e-l+2)+i}else{i=n+i.slice(0,l-e)+"."+i.slice(l-e)}}else{i=n+i}return i}},ne);var ae=n.split;if("ab".split(/(?:ab)*/).length!==2||".".split(/(.?)(.?)/).length!==4||"tesst".split(/(s)*/)[1]==="t"||"test".split(/(?:)/,-1).length!==4||"".split(/.?/).length||".".split(/()()/).length>1){(function(){var t=typeof/()??/.exec("")[1]==="undefined";n.split=function(e,r){var n=this;if(typeof e==="undefined"&&r===0){return[]}if(!m(e)){return ae.call(this,e,r)}var i=[],a=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.extended?"x":"")+(e.sticky?"y":""),o=0,l,f,s,c;e=new RegExp(e.source,a+"g");n+="";if(!t){l=new RegExp("^"+e.source+"$(?!\\s)",a)}r=typeof r==="undefined"?-1>>>0:D.ToUint32(r);f=e.exec(n);while(f){s=f.index+f[0].length;if(s>o){i.push(n.slice(o,f.index));if(!t&&f.length>1){f[0].replace(l,function(){for(var t=1;t1&&f.index=r){break}}if(e.lastIndex===f.index){e.lastIndex++}f=e.exec(n)}if(o===n.length){if(c||!e.test("")){i.push("")}}else{i.push(n.slice(o))}return i.length>r?i.slice(0,r):i}})()}else if("0".split(void 0,0).length){n.split=function We(t,e){if(typeof t==="undefined"&&e===0){return[]}return ae.call(this,t,e)}}var oe=n.replace;var ue=function(){var t=[];"x".replace(/x(.)?/g,function(e,r){t.push(r)});return t.length===1&&typeof t[0]==="undefined"}();if(!ue){n.replace=function _e(t,e){var r=h(e);var n=m(t)&&/\)[*?]/.test(t.source);if(!r||!n){return oe.call(this,t,e)}else{var i=function(r){var n=arguments.length;var i=t.lastIndex;t.lastIndex=0;var a=t.exec(r)||[];t.lastIndex=i;a.push(arguments[n-2],arguments[n-1]);return e.apply(this,a)};return oe.call(this,t,i)}}}var le=n.substr;var fe="".substr&&"0b".substr(-1)!=="b";N(n,{substr:function tr(t,e){return le.call(this,t<0?(t=this.length+t)<0?0:t:t,e)}},fe);var se=" \n\f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003"+"\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028"+"\u2029\ufeff";var ce="\u200b";var pe="["+se+"]";var he=new RegExp("^"+pe+pe+"*");var ve=new RegExp(pe+pe+"*$");var ge=n.trim&&(se.trim()||!ce.trim());N(n,{trim:function er(){if(typeof this==="undefined"||this===null){throw new TypeError("can't convert "+this+" to object")}return String(this).replace(he,"").replace(ve,"")}},ge);if(parseInt(se+"08")!==8||parseInt(se+"0x16")!==22){parseInt=function(t){var e=/^0[xX]/;return function r(n,i){n=String(n).trim();if(!Number(i)){i=e.test(n)?16:10}return t(n,i)}}(parseInt)}});
7 | //# sourceMappingURL=es5-shim.map
--------------------------------------------------------------------------------