From 9c15804bea8e65c8f8760bd4e3d9709d57bb7bbd Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 6 May 2020 13:46:47 +0530 Subject: [PATCH 1/7] Setup for push notifications --- assets/src/config.js | 11 +++ assets/src/serviceWorker.js | 137 ++++++++++++++++++++++++++++++++++++ assets/src/subscription.js | 65 +++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 assets/src/config.js create mode 100644 assets/src/serviceWorker.js create mode 100644 assets/src/subscription.js diff --git a/assets/src/config.js b/assets/src/config.js new file mode 100644 index 000000000..0bca781f0 --- /dev/null +++ b/assets/src/config.js @@ -0,0 +1,11 @@ +// node server +export default { + VAPID_KEY: "BFLXpTODBTbqoZCVaVoswb8rXbYjmrnjSsou7T9-EIkT524zKbk6KtpHuQPXeqrxsr6I8VOARg8u_K9Pyh29MUY", + API_URL: "http://localhost:9000" +} + +// elixir server +// export default { +// VAPID_KEY: "BEajsXSIf77FOxdFKpaL8RrOINCdLYkcwID9uRV_OCBTS_BwOZ4ybx3qdaHcLiqjc6P0HAYQDP4zS77Hq3gKE7Y", +// API_URL: "http://localhost:8085" +// } \ No newline at end of file diff --git a/assets/src/serviceWorker.js b/assets/src/serviceWorker.js new file mode 100644 index 000000000..c273ae3b5 --- /dev/null +++ b/assets/src/serviceWorker.js @@ -0,0 +1,137 @@ +// 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.1/8 is 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 ('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 swFileName = process.env.NODE_ENV === 'production' ? 'service-worker.js' : 'custom-sw.js' + const swUrl = `${publicUrl.origin}/${swFileName}`; + + + 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) + .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(); + }); + } +} diff --git a/assets/src/subscription.js b/assets/src/subscription.js new file mode 100644 index 000000000..588427f79 --- /dev/null +++ b/assets/src/subscription.js @@ -0,0 +1,65 @@ +import subscription from "./config" + +const VapidKey = subscription.VAPID_KEY +const APIUrl = subscription.API_URL +const convertedVapidKey = urlBase64ToUint8Array(VapidKey) + +function urlBase64ToUint8Array(base64String) { + const padding = "=".repeat((4 - base64String.length % 4) % 4) + // eslint-disable-next-line + const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/") + + const rawData = window.atob(base64) + const outputArray = new Uint8Array(rawData.length) + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i) + } + return outputArray +} + +function sendSubscription(subscription) { + return fetch(`${APIUrl}/notifications/subscribe`, { + method: 'POST', + body: JSON.stringify(subscription), + headers: { + 'Content-Type': 'application/json' + } + }) +} + +export function subscribeUser() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(function(registration) { + if (!registration.pushManager) { + console.log('Push manager unavailable.') + return + } + + registration.pushManager.getSubscription().then(function(existedSubscription) { + if (existedSubscription === null) { + console.log('No subscription detected, make a request.') + registration.pushManager.subscribe({ + applicationServerKey: convertedVapidKey, + userVisibleOnly: true, + }).then(function(newSubscription) { + console.log('New subscription added.') + sendSubscription(newSubscription) + }).catch(function(e) { + if (Notification.permission !== 'granted') { + console.log('Permission was not granted.') + } else { + console.error('An error ocurred during the subscription process.', e) + } + }) + } else { + console.log('Existed subscription detected.') + sendSubscription(existedSubscription) + } + }) + }) + .catch(function(e) { + console.error('An error ocurred during Service Worker registration.', e) + }) + } +} From 9a2a23eb792ad8df45705c614f9c04d0969c70ef Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 6 May 2020 15:47:51 +0530 Subject: [PATCH 2/7] Setup for push notifications --- .gitignore | 2 +- assets/src/app.js | 7 +++++++ assets/src/config.js | 10 ++-------- config/dev.exs | 5 +---- lib/orcasite_web/endpoint.ex | 2 +- priv/static/custom-sw.js | 10 ++++++++++ 6 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 priv/static/custom-sw.js diff --git a/.gitignore b/.gitignore index 2d3ab15db..8d8018dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /deps /*.ez -/priv/static/ +# /priv/static/ /rel node_modules/ diff --git a/assets/src/app.js b/assets/src/app.js index 54056e0a2..629d0fbde 100644 --- a/assets/src/app.js +++ b/assets/src/app.js @@ -7,6 +7,9 @@ import apolloClient from './apolloClient' import Root from './components/Root' import ReactGA from 'react-ga' +import * as serviceWorker from './serviceWorker' +import { subscribeUser } from './subscription' + if (ENV.GOOGLE_ANALYTICS_ID) { ReactGA.initialize(ENV.GOOGLE_ANALYTICS_ID) ReactGA.pageview(window.location.pathname + window.location.search); @@ -18,3 +21,7 @@ render( , document.getElementById('root'), ) + +serviceWorker.register() + +subscribeUser() \ No newline at end of file diff --git a/assets/src/config.js b/assets/src/config.js index 0bca781f0..eb9c6c143 100644 --- a/assets/src/config.js +++ b/assets/src/config.js @@ -1,11 +1,5 @@ -// node server + export default { - VAPID_KEY: "BFLXpTODBTbqoZCVaVoswb8rXbYjmrnjSsou7T9-EIkT524zKbk6KtpHuQPXeqrxsr6I8VOARg8u_K9Pyh29MUY", + VAPID_KEY: "PUBLIC_VAPID_KEY", API_URL: "http://localhost:9000" } - -// elixir server -// export default { -// VAPID_KEY: "BEajsXSIf77FOxdFKpaL8RrOINCdLYkcwID9uRV_OCBTS_BwOZ4ybx3qdaHcLiqjc6P0HAYQDP4zS77Hq3gKE7Y", -// API_URL: "http://localhost:8085" -// } \ No newline at end of file diff --git a/config/dev.exs b/config/dev.exs index bcb456134..a88f39079 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -11,10 +11,7 @@ config :orcasite, OrcasiteWeb.Endpoint, debug_errors: true, code_reloader: true, check_origin: false, - watchers: [ - npm: ["run", "watch", - cd: Path.expand("../assets", __DIR__) - ]] + watchers: [node: ["node_modules/webpack-dev-server/bin/webpack-dev-server.js", "--colors", "--stdin", cd: Path.expand("../assets", __DIR__)]] # ## SSL Support # diff --git a/lib/orcasite_web/endpoint.ex b/lib/orcasite_web/endpoint.ex index a2474d490..c848b6a29 100644 --- a/lib/orcasite_web/endpoint.ex +++ b/lib/orcasite_web/endpoint.ex @@ -20,7 +20,7 @@ defmodule OrcasiteWeb.Endpoint do at: "/", from: :orcasite, gzip: false, - only: ~w(css fonts images js favicon.ico robots.txt) + only: ~w(css fonts images js favicon.ico robots.txt custom-sw.js service-worker.js) ) # Code reloading can be explicitly enabled under the diff --git a/priv/static/custom-sw.js b/priv/static/custom-sw.js new file mode 100644 index 000000000..d09d59ff9 --- /dev/null +++ b/priv/static/custom-sw.js @@ -0,0 +1,10 @@ +self.addEventListener('push', event => { + const data = event.data.json() + console.log('New notification', data) + const options = { + body: data.body, + } + event.waitUntil( + self.registration.showNotification(data.title, options) + ); +}) From ce8fb90cf674af8bdcd75313cae69fea4d944073 Mon Sep 17 00:00:00 2001 From: Dhananjay Purohit Date: Wed, 6 May 2020 16:17:14 +0530 Subject: [PATCH 3/7] Update README.md --- README.md | 91 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 11e1f1b93..e8ed16ddb 100644 --- a/README.md +++ b/README.md @@ -2,96 +2,11 @@ This repo specifies the web backend and frontend for the [Orcasound app](http://live.orcasound.net) that plays a live audio stream through the user's browser of choice. The backend is an [Elixir](https://elixir-lang.org/) app using the [Phoenix framework](https://phoenixframework.org/). The Phoenix app serves a React app. -## Contributing +### Notifications server setup -Please check out the [CONTRIBUTING](CONTRIBUTING.md) doc for tips on making a successful contribution, as well as learning resources! +https://github.com/DhananjayPurohit/push_server_node -## Getting started - -##### Quick method (e.g. at hackathons): - -**Short-cut: `docker-compose pull && docker-compose up`** - -Note: this assumes you have installed [docker](https://docs.docker.com/v17.09/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/). - -To access the site, run `docker-compose pull && docker-compose up` and wait until the `phoenix_1` container outputs `Compiled successfully`. Then you should find the site available in your browser at [`http://localhost:4000`](http://localhost:4000). - -For details about the quick method of getting the site up and running, [see the Docker configuration guidance](#running-in-docker) below. - -##### Flexible method (e.g. longer-term development) - -If you would like a more flexible method, you can [install the dependencies directly on your machine](#set-up-manually). - -## Running in Docker - -Docker is the quickest way to get the project up and running, especially if you haven't already set up Erlang/Elixir/Node. The only requirement is that you have both [docker](https://docs.docker.com/v17.09/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/) installed on your machine. - -Once you clone the repository, you can just run docker-compose in the root directory. To avoid building locally you can use the `pull` command first to grab the pre-built image from [Docker Hub](https://hub.docker.com/r/orcasound/orcasite). Combining both commands into one line: - -`docker-compose pull && docker-compose up` - -This will pull the pre-built image from [Docker Hub](https://hub.docker.com/r/orcasound/orcasite) along with an image for the database, automatically configure everything, and run the Phoenix server. The orcasite page will be accessible at [`http://localhost:4000`](http://localhost:4000) as soon as the `phoenix_1` container outputs `Compiled successfully`. - -#### Developing in Docker - -At the moment, the `docker-compose` file uses bind mounting for the source files (`assets`, `config`, `lib`, `test`, `mix.exs`, and `mix.lock`), which means if you edit the source on your host file system, the changes will get picked up and hot reloaded in your browser. - -However, please note that installed packages are not shared with the host file system, which means node packages and hexes need to be installed by running `mix deps.get` and `npm install` inside the `phoenix` container. The best way to do this is to use `docker-compose exec` - -`docker-compose exec phoenix bash -c "mix deps.get && cd assets && npm install"` - -The `exec` command can also be used to run any other commands that need to be completed in the container, like `ecto.migrate` and other database operations. - -For more involved development you may want to access `iex`. To do this, stop the containers with `docker-compose stop` and start them back up using - -`docker-compose run --rm --service-ports phoenix iex -S mix phx.server` - -You will now have an interactive access to iex, while the postgres container runs in the background. - -If you need more control over the dev setup, consider [installing everything manually on the local machine](#set-up-manually). - -## Set up manually - -If Docker doesn't suit your needs, you can follow these instructions to get everything running directly on your machine. - -### Requirements - -#### Language - -You will need to install Erlang, Elixir, and Nodejs. You can use a tool like [`asdf`](https://github.com/asdf-vm/asdf) to manage your language dependencies. - -Language-level dependencies can be found under `.tool-versions`. As of this writing, they are: - -``` -erlang 21.0.4 -elixir 1.7.1 -nodejs 10.4.0 -``` - -#### Database - -You will need to install Postgres and setup the `postgres` user with a password. The default connection details are: - -``` -username: "postgres" -password: "postgres" -database: "orcasite_dev" -hostname: "localhost" -port: 5432 -``` - -You can pass in custom values using env variables. Full details can be found in [`dev.exs`](config/dev.exs). - -Orcasite uses [PostGIS](http://postgis.net/) for location data inside of Postgres. To install on MacOS, run - -`brew install postgis` - -As of this writing, the following version numbers are used: - -``` -postgres 10.6 -postgis 2.5.1 -``` +Add generated public vapid key in `assets/src/config.js` ### Installation From 05bddd872a908e915068b406897506224db90da5 Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 6 May 2020 16:30:57 +0530 Subject: [PATCH 4/7] Setup for push notifications --- config/dev.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/dev.exs b/config/dev.exs index a88f39079..bcb456134 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -11,7 +11,10 @@ config :orcasite, OrcasiteWeb.Endpoint, debug_errors: true, code_reloader: true, check_origin: false, - watchers: [node: ["node_modules/webpack-dev-server/bin/webpack-dev-server.js", "--colors", "--stdin", cd: Path.expand("../assets", __DIR__)]] + watchers: [ + npm: ["run", "watch", + cd: Path.expand("../assets", __DIR__) + ]] # ## SSL Support # From 325782ebc875cbac7caecdc1434be1f10711b2eb Mon Sep 17 00:00:00 2001 From: Dhananjay Purohit Date: Wed, 6 May 2020 16:45:33 +0530 Subject: [PATCH 5/7] Update README.md --- README.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e8ed16ddb..11e1f1b93 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,96 @@ This repo specifies the web backend and frontend for the [Orcasound app](http://live.orcasound.net) that plays a live audio stream through the user's browser of choice. The backend is an [Elixir](https://elixir-lang.org/) app using the [Phoenix framework](https://phoenixframework.org/). The Phoenix app serves a React app. -### Notifications server setup +## Contributing -https://github.com/DhananjayPurohit/push_server_node +Please check out the [CONTRIBUTING](CONTRIBUTING.md) doc for tips on making a successful contribution, as well as learning resources! -Add generated public vapid key in `assets/src/config.js` +## Getting started + +##### Quick method (e.g. at hackathons): + +**Short-cut: `docker-compose pull && docker-compose up`** + +Note: this assumes you have installed [docker](https://docs.docker.com/v17.09/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/). + +To access the site, run `docker-compose pull && docker-compose up` and wait until the `phoenix_1` container outputs `Compiled successfully`. Then you should find the site available in your browser at [`http://localhost:4000`](http://localhost:4000). + +For details about the quick method of getting the site up and running, [see the Docker configuration guidance](#running-in-docker) below. + +##### Flexible method (e.g. longer-term development) + +If you would like a more flexible method, you can [install the dependencies directly on your machine](#set-up-manually). + +## Running in Docker + +Docker is the quickest way to get the project up and running, especially if you haven't already set up Erlang/Elixir/Node. The only requirement is that you have both [docker](https://docs.docker.com/v17.09/engine/installation/) and [docker-compose](https://docs.docker.com/compose/install/) installed on your machine. + +Once you clone the repository, you can just run docker-compose in the root directory. To avoid building locally you can use the `pull` command first to grab the pre-built image from [Docker Hub](https://hub.docker.com/r/orcasound/orcasite). Combining both commands into one line: + +`docker-compose pull && docker-compose up` + +This will pull the pre-built image from [Docker Hub](https://hub.docker.com/r/orcasound/orcasite) along with an image for the database, automatically configure everything, and run the Phoenix server. The orcasite page will be accessible at [`http://localhost:4000`](http://localhost:4000) as soon as the `phoenix_1` container outputs `Compiled successfully`. + +#### Developing in Docker + +At the moment, the `docker-compose` file uses bind mounting for the source files (`assets`, `config`, `lib`, `test`, `mix.exs`, and `mix.lock`), which means if you edit the source on your host file system, the changes will get picked up and hot reloaded in your browser. + +However, please note that installed packages are not shared with the host file system, which means node packages and hexes need to be installed by running `mix deps.get` and `npm install` inside the `phoenix` container. The best way to do this is to use `docker-compose exec` + +`docker-compose exec phoenix bash -c "mix deps.get && cd assets && npm install"` + +The `exec` command can also be used to run any other commands that need to be completed in the container, like `ecto.migrate` and other database operations. + +For more involved development you may want to access `iex`. To do this, stop the containers with `docker-compose stop` and start them back up using + +`docker-compose run --rm --service-ports phoenix iex -S mix phx.server` + +You will now have an interactive access to iex, while the postgres container runs in the background. + +If you need more control over the dev setup, consider [installing everything manually on the local machine](#set-up-manually). + +## Set up manually + +If Docker doesn't suit your needs, you can follow these instructions to get everything running directly on your machine. + +### Requirements + +#### Language + +You will need to install Erlang, Elixir, and Nodejs. You can use a tool like [`asdf`](https://github.com/asdf-vm/asdf) to manage your language dependencies. + +Language-level dependencies can be found under `.tool-versions`. As of this writing, they are: + +``` +erlang 21.0.4 +elixir 1.7.1 +nodejs 10.4.0 +``` + +#### Database + +You will need to install Postgres and setup the `postgres` user with a password. The default connection details are: + +``` +username: "postgres" +password: "postgres" +database: "orcasite_dev" +hostname: "localhost" +port: 5432 +``` + +You can pass in custom values using env variables. Full details can be found in [`dev.exs`](config/dev.exs). + +Orcasite uses [PostGIS](http://postgis.net/) for location data inside of Postgres. To install on MacOS, run + +`brew install postgis` + +As of this writing, the following version numbers are used: + +``` +postgres 10.6 +postgis 2.5.1 +``` ### Installation From aa6d3245c60df41cab2791bc2be2ab26a903e1af Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Wed, 6 May 2020 16:54:39 +0530 Subject: [PATCH 6/7] Setup for push notifications --- NOTIFICATIONSETUP.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 NOTIFICATIONSETUP.md diff --git a/NOTIFICATIONSETUP.md b/NOTIFICATIONSETUP.md new file mode 100644 index 000000000..9ae2fd15a --- /dev/null +++ b/NOTIFICATIONSETUP.md @@ -0,0 +1,6 @@ +## Setup for push notifications + +Clone and setup the repo: +https://github.com/DhananjayPurohit/push_server_node + +Add the generated public vapid key in `assets/src/config.js` \ No newline at end of file From eb46a361c703dbff2d6070d8a5ea3a3ac3467024 Mon Sep 17 00:00:00 2001 From: Dhananjay Purohit Date: Sun, 10 May 2020 02:08:06 +0530 Subject: [PATCH 7/7] Update NOTIFICATIONSETUP.md --- NOTIFICATIONSETUP.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/NOTIFICATIONSETUP.md b/NOTIFICATIONSETUP.md index 9ae2fd15a..b744a4bfb 100644 --- a/NOTIFICATIONSETUP.md +++ b/NOTIFICATIONSETUP.md @@ -1,6 +1,11 @@ -## Setup for push notifications +## Setup for push notifications server Clone and setup the repo: + +Elixir: +https://github.com/DhananjayPurohit/push_server + +Node.js: https://github.com/DhananjayPurohit/push_server_node -Add the generated public vapid key in `assets/src/config.js` \ No newline at end of file +Add the generated public vapid key in `assets/src/config.js`