From 3fb17212b73d130092d3fd9859d171f096ba45cf Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sun, 7 Jun 2020 12:54:00 +0200 Subject: [PATCH] Remove Web section from gitignore --- .gitignore | 15 ---- web/fling/public/index.html | 31 +++++++ web/fling/src/index.js | 46 ++++++++++ web/fling/src/redux/actions.js | 0 web/fling/src/redux/reducer.js | 3 + web/fling/src/redux/state.js | 4 + web/fling/src/serviceWorker.js | 141 ++++++++++++++++++++++++++++++ web/fling/src/setupTests.js | 5 ++ web/fling/src/util/flingclient.js | 125 ++++++++++++++++++++++++++ web/fling/src/util/request.js | 36 ++++++++ 10 files changed, 391 insertions(+), 15 deletions(-) create mode 100644 web/fling/public/index.html create mode 100644 web/fling/src/index.js create mode 100644 web/fling/src/redux/actions.js create mode 100644 web/fling/src/redux/reducer.js create mode 100644 web/fling/src/redux/state.js create mode 100644 web/fling/src/serviceWorker.js create mode 100644 web/fling/src/setupTests.js create mode 100644 web/fling/src/util/flingclient.js create mode 100644 web/fling/src/util/request.js diff --git a/.gitignore b/.gitignore index a8dfb23..5e02c19 100644 --- a/.gitignore +++ b/.gitignore @@ -456,21 +456,6 @@ tags # Ignore all local history of files .history -### Web ### -*.asp -*.cer -*.csr -*.css -*.htm -*.html -*.js -*.jsp -*.php -*.rss -*.wasm -*.wat -*.xhtml - ### Windows ### # Windows thumbnail cache files Thumbs.db diff --git a/web/fling/public/index.html b/web/fling/public/index.html new file mode 100644 index 0000000..5d62148 --- /dev/null +++ b/web/fling/public/index.html @@ -0,0 +1,31 @@ + + + + + + + + Fling + + +
+ + + diff --git a/web/fling/src/index.js b/web/fling/src/index.js new file mode 100644 index 0000000..1b67669 --- /dev/null +++ b/web/fling/src/index.js @@ -0,0 +1,46 @@ +import 'core-js'; + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import log from 'loglevel'; + +import { Provider } from 'react-redux'; +import { createStore } from 'redux'; +import reducer from './redux/reducer'; + +import { BrowserRouter } from "react-router-dom"; + +import App from './App'; + +import "./style/fling.scss"; + +import * as serviceWorker from './serviceWorker'; + +/* Logging Setup */ +log.setDefaultLevel(log.levels.TRACE); +if(process.env.REACT_APP_LOGLEVEL) { + log.setLevel(process.env.REACT_APP_LOGLEVEL); +} + +/* Store setup */ +let store = createStore(reducer, + (window.__REDUX_DEVTOOLS_EXTENSION__ + && window.__REDUX_DEVTOOLS_EXTENSION__())); + +/* Fling App Setup */ +ReactDOM.render( + + + + + + + , + document.getElementById('root') +); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister(); diff --git a/web/fling/src/redux/actions.js b/web/fling/src/redux/actions.js new file mode 100644 index 0000000..e69de29 diff --git a/web/fling/src/redux/reducer.js b/web/fling/src/redux/reducer.js new file mode 100644 index 0000000..1e8d47f --- /dev/null +++ b/web/fling/src/redux/reducer.js @@ -0,0 +1,3 @@ +export default function (state = {}, action) { + return; +}; diff --git a/web/fling/src/redux/state.js b/web/fling/src/redux/state.js new file mode 100644 index 0000000..4e6c41c --- /dev/null +++ b/web/fling/src/redux/state.js @@ -0,0 +1,4 @@ +export default { + flings: [], + currentFling: undefined, +}; diff --git a/web/fling/src/serviceWorker.js b/web/fling/src/serviceWorker.js new file mode 100644 index 0000000..b04b771 --- /dev/null +++ b/web/fling/src/serviceWorker.js @@ -0,0 +1,141 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then(registration => { + registration.unregister(); + }) + .catch(error => { + console.error(error.message); + }); + } +} diff --git a/web/fling/src/setupTests.js b/web/fling/src/setupTests.js new file mode 100644 index 0000000..74b1a27 --- /dev/null +++ b/web/fling/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect'; diff --git a/web/fling/src/util/flingclient.js b/web/fling/src/util/flingclient.js new file mode 100644 index 0000000..85b42e8 --- /dev/null +++ b/web/fling/src/util/flingclient.js @@ -0,0 +1,125 @@ +import log from 'loglevel'; +import axios from 'axios'; + +import request from './request'; + +let flingClient = { + + deleteFling: function(flingId) { + return request.delete(`/fling/${flingId}`) + .then(response => log.info(`Deleted fling ${flingId}`)); + }, + + getFlings: function() { + return request.get('/fling') + .then(response => { + log.info(`Got ${response.data.length} flings`); + return response.data; + }); + }, + + putFling: function(flingId, update) { + return request.put(`/fling/${flingId}`, update) + .then(response => log.info(`Updated fling ${flingId}`)); + }, + + postFling: function(newFling) { + return request.post(`/fling`, newFling) + .then(response => log.info(`Created fling ${response.data}`)); + }, + + getFling: function(flingId) { + return request.get(`/fling?flingId=${flingId}`) + .then(response => { + log.info(`Got fling ${flingId}`); + return response.data; + }); + }, + + getFlingByShareId: function(shareId) { + if(!shareId) return Promise.resolve(null); + + return request.get(`/fling?shareId=${shareId}`) + .then(response => { + log.info(`Got fling ${response.data.id}`); + return response.data; + }) + .catch(err => { + if (err.isAxiosError && err.response.status === 404) { + return null; + } + throw err; + }); + }, + + getShareExists: function(shareId) { + if(!shareId) { + console.info("Empty share id, do not check uniqueness"); + return Promise.resolve(true); + } + + return request.get(`/fling/shareExists/${shareId}`) + .then(response => { + if(response.data){ + log.info(`Share with id ${shareId} already exists`); + } else { + log.info(`Share with id ${shareId} does not exist`); + } + return response.data; + }); + }, + + packageFling: function(flingId) { + return request.get(`/fling/${flingId}/package`) + .then(response => { + log.debug("Got fling download id", response.data); + let url = `${process.env.REACT_APP_API}/fling/${flingId}/download/${response.data}`; + log.debug("Download url", url); + return url; + }); + }, +}; + +let artifactClient = { + + getArtifacts: function(flingId) { + return request.get(`/artifacts?flingId=${flingId}`) + .then(response => { + log.info(`Got ${response.data.length} artifacts`); + return response.data; + }); + }, + + postArtifact: function(flingId, artifact, progressIndicator) { + return request.post(`/artifacts/${flingId}`, artifact) + .then(response => { + log.info(`Uploaded artifact: ${response.data}`); + return request.patch(`/artifacts/${response.data.id}`, + {name: artifact.name, size: artifact.size}); + }) + .then(response => { + log.info(`Updated artifact data: ${response.data}`); + return response.data; + }) + .catch(err => log.error(`Error while uploading artifact: ${err}`)); + }, + + deleteArtifact: function(artifactId) { + return request.delete(`/artifacts/${artifactId}`) + .then(response => { + log.info(`Delete artifact ${artifactId}`); + }); + }, + + downloadArtifact: function(artifactId) { + return request.get(`/artifacts/${artifactId}/downloadid`) + .then(response => { + log.debug("Got download id", response.data); + let url = `${process.env.REACT_APP_API}/artifacts/${artifactId}/${response.data}/download`; + log.debug("Download url", url); + return url; + }); + } +}; + +export {flingClient, artifactClient}; diff --git a/web/fling/src/util/request.js b/web/fling/src/util/request.js new file mode 100644 index 0000000..673b171 --- /dev/null +++ b/web/fling/src/util/request.js @@ -0,0 +1,36 @@ +import log from 'loglevel'; +import axios from 'axios'; +import jwtDecode from 'jwt-decode'; + +function Request() { + let request = axios.create({baseURL: process.env.REACT_APP_API}); + if(sessionStorage.getItem('token')) { + request.defaults.headers = {'Authorization': `Bearer ${sessionStorage.getItem('token')}`}; + } + + return request; +} + +function hasClaim(name, value) { + if(!sessionStorage.getItem('token')) return false; + let tokenPayload = jwtDecode(sessionStorage.getItem('token')); + return tokenPayload[name] === value; +} + +function setAuth(token) { + if(token == null){ // reset the auth + sessionStorage.removeItem('token'); + request.defaults.headers = {}; + return; + } + + sessionStorage.setItem('token', token); + request.defaults.headers = {'Authorization': 'Bearer '+sessionStorage.getItem('token')}; +} + +let request = new Request(); +let isOwner = () => hasClaim("sub", "owner"); +let isUser = (shareId) => hasClaim("sub", "user") && hasClaim("sid", shareId); + +export {isOwner, isUser, setAuth}; +export default request;