├── .gitignore ├── README.md ├── Routing Installation ├── README.md ├── init_alpine └── init_raspian └── community-website ├── README.md ├── members ├── models └── member.js ├── package-lock.json ├── package.json ├── public ├── assets │ ├── bridge-logo.png │ └── bridgeicon.png ├── js │ ├── jquery-3.5.0.min.js │ ├── main.js │ ├── members.js │ ├── networklistener.js │ ├── profile.js │ ├── signup.js │ ├── socketio │ │ └── socket.io.js │ └── todos.js └── styles │ └── main.css ├── sass ├── main.css ├── main.css.map ├── style.css ├── style.css.map └── style.scss ├── server.js ├── sessions ├── todos └── views ├── chat.ejs ├── configuration.ejs ├── index.ejs ├── login.ejs ├── members.ejs ├── profile.ejs ├── signup.ejs └── tasks.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bridge to the Internet 2 | *Bridge to the Internet* creates a platform for localized network infrastructure to minimize resources required for networked communication to better serve their local communities during times of intermittent connection. 3 | 4 | [Project Website](https://bridge.markofthelam.com) 5 | 6 | ![Bridge to the Internet Logo](http://sandbox.markofthelam.com/img/titlecard_853x480.gif) 7 | 8 | ## Table of Contents 9 | - [About](#about) 10 | - [How It Works](#how-it-works) 11 | - [Installation Instructions](#installation) 12 | - [Post Installation](#post-installation) 13 | - [Services](#services) 14 | - [Acknowledgements](#acknowledgements) 15 | 16 | ## About 17 | *Bridge to the Internet* is a router image that hosts network bandwidth saving services and robust local area communication applications that activate local area networks to strengthen connection within a local community. With the internet under heavy strain due to the COVID-19 pandemic, it becomes clear that we must maximize our network resources and look for alternatives to internet-based communication. 18 | 19 | It implements, in a single device, a wireless access point, a web-cache server, a DNS sinkhole server, and an intranet web server that hosts resilient communication applications that allow peers to communicate over Wi-Fi if direct communication is not possible and when the internet is inaccessible. The Bridge image is designed to be quickly and easily set up with a one-line configuration script for non-technical network administrators. It is distributed as open-source software with detailed installation instructions for multiple build systems. 20 | 21 | Ultimately, *Bridge to the Internet* seeks to educate about network infrastructure and begin a conversation about the internet's role in local communities to reimagine the sharing of network resources. 22 | 23 | **Internal Web Links**: 24 | - Community Website: `http://bridge` or `http://10.0.0.1` 25 | - Pi-Hole Dashboard: `http://pi.hole:8080/admin` or `http://10.0.0.1:8080/admin` 26 | 27 | ### Included software: 28 | - [Squid](http://www.squid-cache.org) 29 | - [Pi-Hole](https://pi-hole.net/) 30 | - Local web server 31 | - Wireless access point with hostapd 32 | - DHCP Server with dnsmasq 33 | 34 | ## How It Works 35 | ![Router as a bridge cartoon](https://sandbox.markofthelam.com/img/router_853x480.gif) 36 | 37 | *Bridge to the Internet* uses hostapd to create a wireless hotspot that forwards network traffic to the internet using the onboard Wi-Fi module. A wireless access point is automatically set up with the installation of the image and uses dnsmasq as a DHCP server. Internet access forwards traffic to the Ethernet port with iptables. 38 | 39 | ![Web cache server on bridge](https://sandbox.markofthelam.com/img/webcache_853x480.gif) 40 | 41 | To increase internet speeds on a network with limited bandwidth, the *Bridge* implements network infrastructure typically reserved for businesses and institutions. A web cache server is implemented with [Squid](http://www.squid-cache.org), a Forward HTTP proxy. The web cache distributes resources at the network level so that all connected devices can experience a faster internet while minimizing the need for external web requests. 42 | 43 | ![Dns sinkhole exploding as](https://sandbox.markofthelam.com/img/dns_853x480.gif) 44 | 45 | *Bridge* comes configured with [Pi-Hole](https://pi-hole.net/), a DNS sinkhole, that acts as a network ad-blocker where all connected clients benefit from the ad blocker. Network resources are further maximized as requests for advertisements are dropped. 46 | 47 | ![Intranet as a message board](https://sandbox.markofthelam.com/img/message-board_853x480.gif) 48 | 49 | *Bridge* hosts a local webserver with running a community task list for asynchronous communication and a real-time chat for synchronous communication. It provides alternative communication channels that do not depend on internet connectivity. The message boards allow for community organization and a way for network peers to be more connected. 50 | 51 | ![Internet as an island](https://sandbox.markofthelam.com/img/island_853x_480.gif) 52 | 53 | *Bridge* wants to educate about network infrastructure and bring attention to the communities that are formed from the local area network infrastructure, and explore ways to share network resources to benefit all. 54 | 55 | ## Installation 56 | *Bridge* is distributed as a disk image for a Raspberry Pi, version 3B+/4B. Its software is provided as open-source with detailed installation instructions for Windows, Mac OS, and Linux build machines. It requires minimal hardware to operate, using the onboard wifi to create a wireless access point. To provide internet sharing, the network needs an ethernet cable and internet modem or router. 57 | 58 | Instructions for: 59 | 60 | * [Windows OS](#windows) 61 | * [Mac OS](#mac-os) 62 | * [Linux OS](#linux) 63 | 64 | ### Build Requirements 65 | - Raspberry Pi 3B+/ 4B (with on-board WiFi) 66 | - 8GB+ Micro SD card (C10 Preferred) 67 | - Ethernet Cable 68 | - [*Bridge* image](https://drive.google.com/open?id=1dwv18Lyx4Elu50rah06Jb-MrmHzcGZB5) 69 | - Internet connectivity 70 | - Micro SD card reader 71 | - [Balena Etcher](https://www.balena.io/etcher/) 72 | - SSH Client ([PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) for Windows Build Machines, Linux and Mac OS will have a built-in SSH client) 73 | - Internet Modem/ Router 74 | 75 | ![Image of required materials](https://sandbox.markofthelam.com/img/bom.jpg) 76 | 77 | ### Windows 78 | 1. Download and install [Balena Etcher](https://www.balena.io/etcher/) 79 | 2. Download [*Bridge* image](https://drive.google.com/open?id=1dwv18Lyx4Elu50rah06Jb-MrmHzcGZB5) 80 | 3. Insert Micro SD card into build machine 81 | 4. Open Balena Etcher and select *bridge.img.gz* as your image and your SD card as target drive 82 | 5. Confirm your target drive and Flash 83 | ![Screenshot of Balena Etcher](https://sandbox.markofthelam.com/img/balena.png) 84 | 6. When flash is complete eject SD card and insert into Raspberry Pi 85 | 7. Connect Raspberry Pi to internet modem with Ethernet cable and power on the Raspberry Pi 86 | 8. The first boot will take extra time to expand the SD card and initialize software, wait at least 10 minutes 87 | 9. When it is completed a wireless network should be visible named `Bridge WiFi` (If it does not appear please power cycle the Raspberry Pi) 88 | 10. Connect to `Bridge WiFi` with the password `community` 89 | 11. Use PuTTY to SSH into the Raspberry Pi 90 | - Host: `oper@10.0.0.1` 91 | - Port: `22` 92 | - Password: `bridge` 93 | 12. Change WiFi SSID and Password by running `sudo bash configurewifi.sh` in the Raspberry Pi shell 94 | 13. You should now be broadcasting your own WiFi network with Pi Hole DNS sinkhole, Squid Cache, and Node Express Web Server 95 | - The local web server can be accessed in the browser at either `http://bridge` or `http://10.0.0.1` 96 | - The Pi-Hole Dashboard can be accessed in the browser at `http://pi.hole:8080/admin` 97 | 14. Follow post-installation instructions to configure your clients to make use of Squid HTTP proxy. 98 | 99 | ### Mac OS 100 | 1. Download and install [Balena Etcher](https://www.balena.io/etcher/) 101 | 2. Download [*Bridge* image](https://drive.google.com/open?id=1dwv18Lyx4Elu50rah06Jb-MrmHzcGZB5) 102 | 3. Insert Micro SD card into build machine 103 | 4. Open Balena Etcher and select *bridge.img.gz* as your image and your SD card as target drive 104 | 5. Confirm your target drive and Flash 105 | ![Screenshot of Balena Etcher](https://sandbox.markofthelam.com/img/balena.png) 106 | 6. When flash is complete eject SD card and insert into Raspberry Pi 107 | 7. Connect Raspberry Pi to internet modem with Ethernet cable and power on the Raspberry Pi 108 | 8. The first boot will take extra time to expand the SD card and initialize software, wait at least 10 minutes 109 | 9. When it is completed a wireless network should be visible named `Bridge WiFi` (If it doesn't appear please power cycle the Raspberry Pi) 110 | ![Mac WiFi](https://sandbox.markofthelam.com/img/mac-wifi.png) 111 | 10. Connect to `Bridge WiFi` with the password `community` 112 | 11. Use your preferred terminal to SSH into the Raspberry Pi with `ssh oper@bridge` and password: `bridge` 113 | 12. Change WiFi SSID and Password by running `sudo bash configurewifi.sh` in the Raspberry Pi shell 114 | 13. You should now be broadcasting your own WiFi network with Pi Hole DNS sinkhole, Squid Cache, and Node Express Web Server 115 | - The local web server can be accessed in the browser at either `http://bridge` or `http://10.0.0.1` 116 | - The Pi-Hole Dashboard can be accessed in the browser at `http://pi.hole:8080/admin` 117 | 14. Follow post-installation instructions to configure your clients to make use of Squid HTTP proxy. 118 | 119 | ### Linux 120 | 1. Download and install [Balena Etcher](https://www.balena.io/etcher/) 121 | 2. Download [*Bridge* image](https://drive.google.com/open?id=1dwv18Lyx4Elu50rah06Jb-MrmHzcGZB5) 122 | 3. Insert Micro SD card into build machine 123 | 4. Open Balena Etcher and select *bridge.img.gz* as your image and your SD card as target drive 124 | 5. Confirm your target drive and Flash 125 | ![Screenshot of Balena Etcher](https://sandbox.markofthelam.com/img/balena.png) 126 | 6. When flash is complete eject SD card and insert into Raspberry Pi 127 | 7. Connect Raspberry Pi to internet modem with Ethernet cable and power on the Raspberry Pi 128 | 8. The first boot will take extra time to expand the SD card and initialize software, wait at least 10 minutes 129 | 9. When it is completed a wireless network should be visible named `Bridge WiFi` 130 | ![Bridge WiFi Network Manager](https://sandbox.markofthelam.com/img/bridge-linux.png) 131 | 10. Connect to `Bridge WiFi` with the password `community` 132 | 11. Use your preferred terminal to SSH into the Raspberry Pi with `ssh oper@bridge` and password: `bridge` 133 | 12. Change WiFi SSID and Password by running `sudo bash configurewifi.sh` in the Raspberry Pi shell 134 | 13. You should now be broadcasting your own WiFi network with Pi Hole DNS sinkhole, Squid Cache, and Node Express Web Server 135 | - The local web server can be accessed in the browser at either `http://bridge` or `http://10.0.0.1` 136 | - The Pi-Hole Dashboard can be accessed in the browser at `http://pi.hole:8080/admin` 137 | 14. Follow post-installation instructions to configure your clients to make use of Squid HTTP proxy. 138 | 139 | ## Post Installation 140 | To make use of Squid HTTP Web Cache you must configure your client devices to use the router as the HTTP Proxy. This enables Squid Cache to act as a proxy server allowing all connected to use the network cache server which will allow faster web requests for connected clients. It is optimal to configure the proxy in your system settings so that all applications running your device. The specific instructions will vary between operating systems, but you can use Chrome Browser to navigate to your system proxy settings. 141 | 142 | ### Computer Proxy Setup 143 | #### Chrome/ Chromium Proxy Setup 144 | Chromium uses your system proxy settings. When changed it will affect all applications on your machine. 145 | 146 | 1. Open Chrome and open Preferences panel 147 | 2. Navigate to Advanced > Systems page 148 | 3. Manually configure proxy with the router IP address as your proxy server 149 | - HTTP Proxy: `10.0.0.1` 150 | - Port: `3128` 151 | ![Mac OS System Settings](https://sandbox.markofthelam.com/img/mac-systemsettings.png) 152 | 4. You can test if it is operating properly by visiting a bogus URL (ldfkjalgkjalj.com). If you receive an error from Squid it means your device is sending requests through the proxy server. Alternatively you can run a packet capture to see HTTP requests are going through port 3128. 153 | 5. You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 154 | ![Squid Cache Error Page](https://sandbox.markofthelam.com/img/squiderror.png) 155 | 156 | #### Firefox Proxy Setup 157 | You can manually set the router as the proxy server on Firefox Browser. You will do this in the browser's preferences page. 158 | 159 | 1. Open Firefox Browser and go to Preferences panel 160 | 2. Navigate to Network Settings and select *settings* 161 | 3. Select Manual Proxy Configuration and set: 162 | - HTTP Proxy: `10.0.0.1` 163 | - Port: `3128` 164 | 4. You can test if it is operating properly by visiting a bogus URL (ldfkjalgkjalj.com). If you receive an error from Squid it means your device is sending requests through the proxy server. Alternatively you can run a packet capture to see HTTP requests are going through port 3128. 165 | 5. You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 166 | ![Squid Cache Error Page](https://sandbox.markofthelam.com/img/squiderror.png) 167 | 168 | ### Mobile Devices 169 | #### IPhone 170 | 1. Navigate WiFi Settings 171 | ![WiFi Settings](https://sandbox.markofthelam.com/img/ios1.jpg) 172 | 2. Select to Bridge WiFi options 173 | ![Bridge Wifi Options IOS](https://sandbox.markofthelam.com/img/ios2.jpg) 174 | 3. Select Configure Proxy 175 | 4. Enable Manual Proxy 176 | - Server: `10.0.0.1` 177 | - Port: `3128` 178 | 4. You can test if it is operating properly by visiting a bogus URL (ldfkjalgkjalj.com). If you receive an error from Squid it means your device is sending requests through the proxy server. Alternatively you can run a packet capture to see HTTP requests are going through port 3128. 179 | 5. You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 180 | ![Squid Cache Error Page](https://sandbox.markofthelam.com/img/squiderror.png) 181 | 182 | 183 | ## Services 184 | ### Pi Hole 185 | *Bridge WiFi* has Pi-Hole pre-configured. Pi-Hole is a DNS Sinkhole which acts as a network level ad-blocker. It greatly improves network speeds by re-directing malicious and advertisement dns requests. Please view the [Pi Hole Website](https://pi-hole.net/) for instructions for advanced configurations. 186 | 187 | The Pi-Hole web dashboard can be viewed at: 188 | - `http://pi.hole:8080/admin` or `http://10.0.0.1:8080/admin` 189 | ![Pi-Hole Dashboard](https://sandbox.markofthelam.com/img/piholedashboard.png) 190 | 191 | ### Squid Cache 192 | [Squid](http://www.squid-cache.org/) is a caching proxy for the Web supporting HTTP, HTTPS, FTP, and more. It reduces bandwidth and improves response times by caching and reusing frequently-requested web pages 193 | 194 | ## Acknowledgements 195 | - [Mimi Onuoha](https://github.com/MimiOnuoha/), Thesis Advisor 196 | - [Alden Jones](https://github.com/miamiww), Thesis Resident 197 | - [Ellen Nickles](https://github.com/ellennickles), Thesis Resident 198 | - [Tom Igoe](https://github.com/tigoe), Advisor 199 | - [Anthony Bui](https://github.com/epylinkn) 200 | - [Luming Hao](https://github.com/lh00000000) 201 | - [Tushar Goyal](https://github.com/asd0999) 202 | 203 | ### Powered by 204 | ![Squid Cache Icon](http://www.squid-cache.org/Artwork/SN1.png) 205 | ![Pi Hole Icon](https://upload.wikimedia.org/wikipedia/en/thumb/1/15/Pi-hole_vector_logo.svg/43px-Pi-hole_vector_logo.svg.png) 206 | 207 | -------------------------------------------------------------------------------- /Routing Installation/README.md: -------------------------------------------------------------------------------- 1 | # Setup a basic router on 2 | 3 | These scripts will check for software dependencies, configure routing files, and create a wireless access point. Please run the script for your operating system. 4 | 5 | ## Requirements 6 | - Raspberry Pi with onbard WiFi 7 | - Ethernet connection 8 | - Software packages installed: 9 | - hostapd 10 | - dnsmasq 11 | - iptables 12 | 13 | -------------------------------------------------------------------------------- /Routing Installation/init_alpine: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ######################################## 4 | ######################################## 5 | #### ### 6 | #### ALPINE ROUTER CONFIGURATION ### 7 | #### ### 8 | ######################################## 9 | ######################################## 10 | 11 | echo "Checking for dependencies..." 12 | 13 | HOSTAPD=$(apk info | grep hostapd) 14 | DNSMASQ=$(apk info | grep dnsmasq) 15 | IPTABLES=$(apk info | grep iptables) 16 | 17 | if [ "$HOSTAPD" = "" ];then 18 | echo "Adding hostapd" 19 | apk add hostapd 20 | else 21 | service hostapd stop 22 | fi 23 | 24 | if [ "$DNSMASQ" = "" ];then 25 | echo "Adding dnsmasq" 26 | apk add dnsmasq 27 | else 28 | service dnsmasq stop 29 | fi 30 | 31 | if [ "$IPTABLES" = "" ];then 32 | echo "Adding adding iptables" 33 | apk add iptables 34 | fi 35 | 36 | echo "Moving config files..." 37 | 38 | mv files/hostapd.conf /etc/hostapd/hostapd.conf 39 | mv files/dnsmasq.conf /etc/dnsmasq.conf 40 | mv files/interfaces /etc/network/interfaces 41 | 42 | echo "Configure iptables..." 43 | 44 | iptables --flush 45 | iptables --table nat --flush 46 | iptables --delete-chain 47 | iptables --table nat --delete-chain 48 | iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE 49 | iptables --append FORWARD --in-interface wlan0 -j ACCEPT 50 | 51 | sysctl -w net.ipv4.ip_forward=1 52 | 53 | ################################################# 54 | ############ SET UP THE NETWORK ################# 55 | ################################################# 56 | 57 | 58 | ################# START WIFI #################### 59 | 60 | if [ "$(ip link | grep wlan0)" ] 61 | then 62 | echo "Killing WiFi." 63 | ifconfig wlan0 down 64 | sleep 1s 65 | fi 66 | 67 | echo "Starting wifi." 68 | ifconfig wlan0 up 10.0.0.1 netmask 255.255.255.0 69 | sleep 2s 70 | 71 | echo "Starting wireless access point." 72 | service hostapd start 73 | service dnsmasq start 74 | 75 | -------------------------------------------------------------------------------- /Routing Installation/init_raspian: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ######################################## 4 | ######################################## 5 | ### ### 6 | ### RASPIAN ROUTER CONFIGURATION ### 7 | ### ### 8 | ######################################## 9 | ############################EMCEELAMB### 10 | 11 | if [[ $(/usr/bin/id -u) -ne 0 ]]; then 12 | echo "Not running as root" 13 | exit 14 | fi 15 | 16 | echo "Checking for dependencies..." 17 | 18 | HOSTAPD=$(which hostapd | grep hostapd) 19 | DNSMASQ=$(which dnsmasq | grep dnsmasq) 20 | IPTABLES=$(which iptables | grep iptables) 21 | 22 | if [ "$HOSTAPD" = "" ];then 23 | echo "Adding hostapd" 24 | sudo apt install hostapd 25 | sed "/^DAEMON_CONF/ { s/^#//; s%=.*%=\"/etc/hostapd/hostapd.conf\"%; }" /etc/default/hostapd 26 | else 27 | sudo killall hostapd 28 | fi 29 | 30 | if [ "$DNSMASQ" = "" ];then 31 | echo "Adding dnsmasq" 32 | sudo apt install dnsmasq 33 | else 34 | sudo killall dnsmasq 35 | fi 36 | 37 | if [ "$IPTABLES" = "" ];then 38 | echo "Adding adding iptables" 39 | sudo apt install iptables 40 | sudo apt install iptables-persistent 41 | fi 42 | 43 | echo "Moving config files..." 44 | 45 | sudo cp files/hostapd.conf /etc/hostapd/hostapd.conf 46 | sudo cp files/hostapd /etc/default/hostapd 47 | sudo cp files/dnsmasq.conf /etc/dnsmasq.conf 48 | sudo cp files/interfaces /etc/network/interfaces 49 | sudo cp files/sysctl.conf /etc/sysctl.conf 50 | 51 | echo "Configure dns..." 52 | 53 | if [ -z "$(ps -e | grep dnsmasq)" ] 54 | then 55 | dnsmasq 56 | fi 57 | 58 | echo "Configure iptables..." 59 | 60 | iptables --flush 61 | iptables --table nat --flush 62 | iptables --delete-chain 63 | iptables --table nat --delete-chain 64 | iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE 65 | iptables --append FORWARD --in-interface wlan0 -j ACCEPT 66 | 67 | sysctl -w net.ipv4.ip_forward=1 68 | 69 | ################################################## 70 | ############# SET UP THE NETWORK ################# 71 | ################################################## 72 | 73 | 74 | ################## START WIFI #################### 75 | 76 | if [ "$(ip link | grep wlan0)" ] 77 | then 78 | echo "Killing WiFi." 79 | ifconfig wlan0 down 80 | sleep 1s 81 | fi 82 | 83 | echo "Starting wifi." 84 | ifconfig wlan0 up 10.0.0.1 netmask 255.255.255.0 85 | sleep 2s 86 | 87 | echo "Starting wireless access point." 88 | hostapd /etc/hostapd/hostapd.conf 1> /dev/null 89 | ~ 90 | ~ 91 | -------------------------------------------------------------------------------- /community-website/README.md: -------------------------------------------------------------------------------- 1 | # Bridge Community website 2 | This is the website pre-installed on the Bridge router image. 3 | It contains: 4 | - Community Task List/ Message Board 5 | - Real Time Text Chta 6 | - Members list 7 | - HTTP Cache Configuration 8 | 9 | ## Installation 10 | To run the website locally without the bridge image please clone this repository: 11 | ``` 12 | git clone https://github.com/emceelamb/bridge 13 | cd community-website 14 | npm install 15 | ``` 16 | 17 | To run: 18 | `npm run dev` 19 | 20 | To run forever use [pm2](https://github.com/Unitech/pm2). 21 | 22 | -------------------------------------------------------------------------------- /community-website/members: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emceelamb/bridge/99ee90cc8a41e3538661e430da1e66f7328cb4d2/community-website/members -------------------------------------------------------------------------------- /community-website/models/member.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emceelamb/bridge/99ee90cc8a41e3538661e430da1e66f7328cb4d2/community-website/models/member.js -------------------------------------------------------------------------------- /community-website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "dev": "nodemon server.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cookie-parser": "^1.4.5", 15 | "dotenv": "^8.2.0", 16 | "ejs": "^3.0.2", 17 | "express": "^4.17.1", 18 | "express-session": "^1.17.0", 19 | "http": "0.0.1-security", 20 | "nedb": "^1.8.0", 21 | "nedb-session-store": "^1.1.2", 22 | "nodemon": "^2.0.2", 23 | "socket.io": "^2.3.0", 24 | "uuid": "^7.0.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /community-website/public/assets/bridge-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emceelamb/bridge/99ee90cc8a41e3538661e430da1e66f7328cb4d2/community-website/public/assets/bridge-logo.png -------------------------------------------------------------------------------- /community-website/public/assets/bridgeicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emceelamb/bridge/99ee90cc8a41e3538661e430da1e66f7328cb4d2/community-website/public/assets/bridgeicon.png -------------------------------------------------------------------------------- /community-website/public/js/main.js: -------------------------------------------------------------------------------- 1 | class Todos { 2 | constructor() { 3 | this.baseurl = '/api/v1/todos'; 4 | this.todos = []; 5 | this.$todos = document.querySelector('.todo-list'); 6 | this.$form = document.querySelector('.todo-form') 7 | } 8 | 9 | async init() { 10 | await this.updateTodos(); 11 | this.$form.addEventListener('submit', async evt => { 12 | evt.preventDefault(); 13 | await this.createTodo(); 14 | }); 15 | } 16 | 17 | async getTodos() { 18 | let data = await fetch (this.baseurl); 19 | data = await data.json(); 20 | this.todos = data; 21 | let incomplete =[]; 22 | let complete =[]; 23 | let toDoList=[]; 24 | 25 | for(var item of this.todos){ 26 | if(item.status=="incomplete"){incomplete.push(item)} 27 | if(incomplete.length == 6)break 28 | 29 | } 30 | // this.todos.forEach((item)=>{ 31 | // if(item.status=="incomplete"){incomplete.push(item)} 32 | // if(item.status=="complete"){complete.push(item)} 33 | // console.log(incomplete.length, "length of in") 34 | // if(incomplete.length == 6)break 35 | 36 | // }) 37 | 38 | if(incomplete.length!=0){ 39 | incomplete.sort((a,b)=>{ 40 | return a.date.localeCompare(b.date); 41 | }); 42 | for(let i = 0; i < incomplete.length;i++){ 43 | toDoList.push(incomplete[i]) 44 | } 45 | } 46 | 47 | if(complete.length!=0){ 48 | console.log(complete) 49 | complete.sort((a,b)=>{ 50 | return a.date.localeCompare(b.date); 51 | }); 52 | for(let i = 0; i < complete.length;i++){ 53 | toDoList.push(complete[i]) 54 | } 55 | } 56 | 57 | this.todos=toDoList; 58 | console.log(this.todos, "getTodos") 59 | await this.renderTodos(); 60 | } 61 | 62 | async createTodo(){ 63 | try { 64 | const newData = { 65 | todo: this.$form.todo.value, 66 | status: 'incomplete', 67 | date: new Date().toLocaleString() 68 | }; 69 | 70 | const options = { 71 | method: 'POST', 72 | headers: { 73 | Accept: 'application/json', 74 | 'Content-Type': 'application/json' 75 | }, 76 | body: JSON.stringify(newData) 77 | }; 78 | let data = await fetch(this.baseurl,options); 79 | data = await data.json(); 80 | await this.updateTodos(); 81 | } catch(error){ 82 | console.error(error); 83 | } 84 | } 85 | 86 | async updateTodo(id, newData) { 87 | try { 88 | const options = { 89 | method: 'PUT', 90 | headers: { 91 | Accept: 'application/json', 92 | 'Content-Type': 'application/json' 93 | }, 94 | body: JSON.stringify(newData) 95 | }; 96 | let data = await fetch(this.baseurl + `/${id}`, options); 97 | data = await data.json(); 98 | await this.updateTodos(); 99 | } catch (error) { 100 | console.error(error); 101 | } 102 | } 103 | 104 | async deleteTodo(id) { 105 | try { 106 | const options = { 107 | method: 'DELETE' 108 | }; 109 | let data = await fetch(this.baseurl + `/${id}`, options); 110 | data = await data.json(); 111 | this.updateTodos(); 112 | } catch (error) { 113 | console.error(error); 114 | } 115 | } 116 | 117 | async updateTodos() { 118 | await this.getTodos(); 119 | this.renderTodos(); 120 | } 121 | 122 | renderTodos() { 123 | this.$todos.innerHTML =''; 124 | this.todos.forEach(item =>{ 125 | this.$todos.innerHTML += ` 126 |
  • 127 |

    ${item.user}

    128 |

    ${item.date}

    129 |
    130 | 131 | ${item.status} 135 |
    136 | 137 |
  • 138 | `; 139 | }); 140 | document.querySelectorAll('.todo-item').forEach(item =>{ 141 | item.addEventListener('click', this.handleEditOrDelete.bind(this)); 142 | }); 143 | // document.querySelectorAll('.todo-toggle').forEach(item =>{ 144 | // item.addEventListener('click', this.toggleStatus.bind(this)); 145 | // }); 146 | } 147 | 148 | async toggleStatus(evt){ 149 | let itemId = evt.target.parentElement.parentElement.parentElement.id 150 | let listItem = evt.target.parentElement.parentElement; 151 | let status; 152 | if (evt.target.checked){ 153 | status="complete"; 154 | } else { 155 | status="incomplete"; 156 | } 157 | console.log(evt.target.checked) 158 | const updateData = { 159 | todo: listItem.children[0].value, 160 | status: status, 161 | user: listItem.children[0].innerHTML, 162 | date: listItem.children[1].innerHTML, 163 | }; 164 | 165 | await this.updateTodo(itemId, updateData); 166 | } 167 | async handleEditOrDelete(evt) { 168 | { 169 | const $clickedButton = evt.target; 170 | const $listItem = evt.currentTarget; 171 | 172 | if($clickedButton.classList.contains('todo-item__delete')){ 173 | await this.deleteTodo($listItem.id); 174 | console.log('delete', $listItem, $listItem.id); 175 | } else if ($clickedButton.classList.contains('todo-item__edit')) { 176 | const form = $listItem.children[2]; 177 | // console.log(form, "form") 178 | // console.log($listItem.children[0].innerHTML, "list") 179 | let status; 180 | if (form.children[1].children[0].checked){ 181 | status="complete"; 182 | } else { 183 | status="incomplete"; 184 | } 185 | const updateData = { 186 | todo: form.todo.value, 187 | status: status, 188 | user: $listItem.children[0].innerHTML, 189 | date: $listItem.children[1].innerHTML, 190 | }; 191 | console.log(updateData) 192 | await this.updateTodo($listItem.id, updateData); 193 | } 194 | } 195 | } 196 | } 197 | 198 | 199 | window.addEventListener('DOMContentLoaded', async () =>{ 200 | const todos = new Todos(); 201 | await todos.init(); 202 | }); 203 | -------------------------------------------------------------------------------- /community-website/public/js/members.js: -------------------------------------------------------------------------------- 1 | class Members { 2 | constructor() { 3 | this.baseurl = '/api/v1/members'; 4 | this.members = []; 5 | this.$membersList = document.querySelector('.members-list'); 6 | } 7 | 8 | async init() { 9 | await this.updateMembers(); 10 | } 11 | 12 | async getMembers() { 13 | let data = await fetch (this.baseurl); 14 | data = await data.json(); 15 | this.members = data; 16 | await this.renderMembers(); 17 | } 18 | 19 | async createTodo(){ 20 | try { 21 | const newData = { 22 | todo: this.$form.todo.value, 23 | status: 'incomplete' 24 | }; 25 | const options = { 26 | method: 'POST', 27 | headers: { 28 | Accept: 'application/json', 29 | 'Content-Type': 'application/json' 30 | }, 31 | body: JSON.stringify(newData) 32 | }; 33 | let data = await fetch(this.baseurl,options); 34 | data = await data.json(); 35 | await this.updateTodos(); 36 | } catch(error){ 37 | console.error(error); 38 | } 39 | } 40 | 41 | async updateTodo(id, newData) { 42 | try { 43 | const options = { 44 | method: 'PUT', 45 | headers: { 46 | Accept: 'application/json', 47 | 'Content-Type': 'application/json' 48 | }, 49 | body: JSON.stringify(newData) 50 | }; 51 | let data = await fetch(this.baseurl + `/${id}`, options); 52 | data = await data.json(); 53 | await this.updateTodos(); 54 | } catch (error) { 55 | console.error(error); 56 | } 57 | } 58 | 59 | async deleteTodo(id) { 60 | try { 61 | const options = { 62 | method: 'DELETE' 63 | }; 64 | let data = await fetch(this.baseurl + `/${id}`, options); 65 | data = await data.json(); 66 | this.updateTodos(); 67 | } catch (error) { 68 | console.error(error); 69 | } 70 | } 71 | 72 | async updateMembers() { 73 | await this.getMembers(); 74 | this.renderMembers(); 75 | } 76 | 77 | renderMembers() { 78 | let tempHTML ='
    Name
    Affiliation
    ' 79 | // this.$membersList.innerHTML ='
    Name
    Affiliation
    ' 80 | this.members.forEach(member =>{ 81 | tempHTML += ` 82 |
    83 |
    ${member.name}
    84 |
    ${member.id}
    85 |
    86 | `; 87 | }); 88 | tempHTML += '
    ' 89 | this.$membersList.innerHTML = tempHTML 90 | console.log(this.$membersList.innerHTML) 91 | // document.querySelectorAll('.todo-item').forEach(item =>{ 92 | // item.addEventListener('click', this.handleEditOrDelete.bind(this)); 93 | // }); 94 | } 95 | 96 | 97 | async handleEditOrDelete(evt) { 98 | { 99 | const $clickedButton = evt.target; 100 | const $listItem = evt.currentTarget; 101 | 102 | if($clickedButton.classList.contains('todo-item__delete')){ 103 | await this.deleteTodo($listItem.id); 104 | console.log('delete', $listItem, $listItem.id); 105 | } else if ($clickedButton.classList.contains('todo-item__edit')) { 106 | const form = $listItem.firstElementChild; 107 | 108 | const updateData = { 109 | todo: form.todo.value, 110 | status: form.status.value 111 | }; 112 | console.log(updateData); 113 | await this.updateTodo($listItem.id, updateData); 114 | console.log('edit', $listItem.id); 115 | } 116 | } 117 | } 118 | } 119 | 120 | window.addEventListener('DOMContentLoaded', async () =>{ 121 | const members = new Members(); 122 | await members.init(); 123 | }); 124 | -------------------------------------------------------------------------------- /community-website/public/js/networklistener.js: -------------------------------------------------------------------------------- 1 | socket.on('connect', function() { 2 | socket.emit('my event', "Connected Device!") 3 | }) 4 | 5 | socket.on('server response', function(res){ 6 | $('#peers').empty(); 7 | let ips = res.split(",") 8 | console.log(ips) 9 | ips.forEach((ip)=>{ 10 | ipAdd= ip.split(":"); 11 | $('#peers').append( 12 | ` 13 |
  • ${ipAdd[3]}
  • 14 | ` 15 | ) 16 | }) 17 | }) 18 | 19 | socket.on('chatmessage', function (data) { 20 | console.log(data); 21 | //customize-> make line break, clear input 22 | let otherMsg = document.getElementById('messages'); 23 | let newOtherMsg = document.createElement('p'); 24 | newOtherMsg.innerHTML = `${data}`; 25 | newOtherMsg.classList.add('newOtherMsg'); 26 | otherMsg.appendChild(newOtherMsg); 27 | // document.getElementById('message').value = ` `; 28 | $('#messages').animate({ 29 | scrollTop: $("#messages").offset().top 30 | }, 0) 31 | }); 32 | //get topic from server 33 | socket.on('giveTopic', function (topic) { 34 | console.log(topic); 35 | }) 36 | 37 | var sendmessage = function (message) { 38 | console.log("chatmessage: " + message); 39 | socket.emit('chatmessage', message); 40 | //customize-> make line break, clear input 41 | let myMsg = document.getElementById('messages'); 42 | let newMyMsg = document.createElement('p'); 43 | newMyMsg.innerHTML = `${message}`; 44 | newMyMsg.classList.add('newMyMsg'); 45 | myMsg.appendChild(newMyMsg); 46 | document.getElementById('message').value = ` `; 47 | $('#messages').animate({ 48 | scrollTop: $("#messages").offset().top 49 | }, 0) 50 | }; 51 | -------------------------------------------------------------------------------- /community-website/public/js/profile.js: -------------------------------------------------------------------------------- 1 | class Todos { 2 | constructor() { 3 | this.baseurl = '/api/v1/todos'; 4 | this.todos = []; 5 | this.$todos = document.querySelector('.todo-list'); 6 | this.$form = document.querySelector('.todo-form') 7 | } 8 | 9 | async init() { 10 | await this.updateTodos(); 11 | this.$form.addEventListener('submit', async evt => { 12 | evt.preventDefault(); 13 | await this.createTodo(); 14 | }); 15 | } 16 | 17 | async getTodos() { 18 | let data = await fetch (this.baseurl); 19 | data = await data.json(); 20 | this.todos = data; 21 | let incomplete =[]; 22 | let complete =[]; 23 | let toDoList=[]; 24 | 25 | this.todos.forEach((item)=>{ 26 | if(item.user == username){ 27 | console.log("hi") 28 | if(item.status=="incomplete"){incomplete.push(item)} 29 | if(item.status=="complete"){complete.push(item)} 30 | } 31 | }) 32 | 33 | if(incomplete.length!=0){ 34 | incomplete.sort((a,b)=>{ 35 | return a.date.localeCompare(b.date); 36 | }); 37 | for(let i = 0; i < incomplete.length;i++){ 38 | toDoList.push(incomplete[i]) 39 | } 40 | } 41 | 42 | if(complete.length!=0){ 43 | console.log(complete) 44 | complete.sort((a,b)=>{ 45 | return a.date.localeCompare(b.date); 46 | }); 47 | for(let i = 0; i < complete.length;i++){ 48 | toDoList.push(complete[i]) 49 | } 50 | } 51 | 52 | this.todos=toDoList; 53 | console.log(this.todos, "getTodos") 54 | await this.renderTodos(); 55 | } 56 | 57 | async createTodo(){ 58 | try { 59 | const newData = { 60 | todo: this.$form.todo.value, 61 | status: 'incomplete', 62 | date: new Date().toLocaleString() 63 | }; 64 | 65 | const options = { 66 | method: 'POST', 67 | headers: { 68 | Accept: 'application/json', 69 | 'Content-Type': 'application/json' 70 | }, 71 | body: JSON.stringify(newData) 72 | }; 73 | let data = await fetch(this.baseurl,options); 74 | data = await data.json(); 75 | await this.updateTodos(); 76 | } catch(error){ 77 | console.error(error); 78 | } 79 | } 80 | 81 | async updateTodo(id, newData) { 82 | try { 83 | const options = { 84 | method: 'PUT', 85 | headers: { 86 | Accept: 'application/json', 87 | 'Content-Type': 'application/json' 88 | }, 89 | body: JSON.stringify(newData) 90 | }; 91 | let data = await fetch(this.baseurl + `/${id}`, options); 92 | data = await data.json(); 93 | await this.updateTodos(); 94 | } catch (error) { 95 | console.error(error); 96 | } 97 | } 98 | 99 | async deleteTodo(id) { 100 | try { 101 | const options = { 102 | method: 'DELETE' 103 | }; 104 | let data = await fetch(this.baseurl + `/${id}`, options); 105 | data = await data.json(); 106 | this.updateTodos(); 107 | } catch (error) { 108 | console.error(error); 109 | } 110 | } 111 | 112 | async updateTodos() { 113 | await this.getTodos(); 114 | this.renderTodos(); 115 | } 116 | 117 | renderTodos() { 118 | this.$todos.innerHTML =''; 119 | this.todos.forEach(item =>{ 120 | this.$todos.innerHTML += ` 121 |
  • 122 |

    ${item.user}

    123 |

    ${item.date}

    124 |
    125 | 126 | ${item.status} 130 |
    131 | | 132 |
  • 133 | `; 134 | }); 135 | document.querySelectorAll('.todo-item').forEach(item =>{ 136 | item.addEventListener('click', this.handleEditOrDelete.bind(this)); 137 | }); 138 | // document.querySelectorAll('.todo-toggle').forEach(item =>{ 139 | // item.addEventListener('click', this.toggleStatus.bind(this)); 140 | // }); 141 | } 142 | 143 | async toggleStatus(evt){ 144 | let itemId = evt.target.parentElement.parentElement.parentElement.id 145 | let listItem = evt.target.parentElement.parentElement; 146 | let status; 147 | if (evt.target.checked){ 148 | status="complete"; 149 | } else { 150 | status="incomplete"; 151 | } 152 | console.log(evt.target.checked) 153 | const updateData = { 154 | todo: listItem.children[0].value, 155 | status: status, 156 | user: listItem.children[0].innerHTML, 157 | date: listItem.children[1].innerHTML, 158 | }; 159 | 160 | await this.updateTodo(itemId, updateData); 161 | } 162 | async handleEditOrDelete(evt) { 163 | { 164 | const $clickedButton = evt.target; 165 | const $listItem = evt.currentTarget; 166 | 167 | if($clickedButton.classList.contains('todo-item__delete')){ 168 | await this.deleteTodo($listItem.id); 169 | console.log('delete', $listItem, $listItem.id); 170 | } else if ($clickedButton.classList.contains('todo-item__edit')) { 171 | const form = $listItem.children[2]; 172 | // console.log(form, "form") 173 | // console.log($listItem.children[0].innerHTML, "list") 174 | let status; 175 | if (form.children[1].children[0].checked){ 176 | status="complete"; 177 | } else { 178 | status="incomplete"; 179 | } 180 | const updateData = { 181 | todo: form.todo.value, 182 | status: status, 183 | user: $listItem.children[0].innerHTML, 184 | date: $listItem.children[1].innerHTML, 185 | }; 186 | // console.log(updateData) 187 | await this.updateTodo($listItem.id, updateData); 188 | } 189 | } 190 | } 191 | } 192 | 193 | 194 | window.addEventListener('DOMContentLoaded', async () =>{ 195 | const todos = new Todos(); 196 | await todos.init(); 197 | }); 198 | -------------------------------------------------------------------------------- /community-website/public/js/signup.js: -------------------------------------------------------------------------------- 1 | class Signup { 2 | constructor() { 3 | this.members = []; 4 | this.$membersList = document.querySelector('.members-list'); 5 | this.baseurl = '/api/v1/members'; 6 | this.$form = document.querySelector('.signup-form') 7 | } 8 | 9 | async init() { 10 | this.$form.addEventListener('submit', async evt => { 11 | evt.preventDefault(); 12 | console.log("click") 13 | await this.createMember(); 14 | }); 15 | } 16 | 17 | async getMembers() { 18 | let data = await fetch (this.baseurl); 19 | data = await data.json(); 20 | this.members = data; 21 | await this.renderMembers(); 22 | } 23 | 24 | async createMember(){ 25 | try { 26 | const newData = { 27 | username: this.$form.username.value.trim(), 28 | name: this.$form.name.value, 29 | id: this.$form.id.value, 30 | password: this.$form.password.value, 31 | }; 32 | const options = { 33 | method: 'POST', 34 | headers: { 35 | Accept: 'application/json', 36 | 'Content-Type': 'application/json' 37 | }, 38 | body: JSON.stringify(newData) 39 | }; 40 | 41 | console.log(newData, "createmem"); 42 | 43 | let data = await fetch(this.baseurl,options); 44 | data = await data.json(); 45 | } catch(error){ 46 | console.error(error); 47 | } 48 | } 49 | 50 | 51 | renderMembers() { 52 | this.$membersList.innerHTML =''; 53 | this.members.forEach(item =>{ 54 | this.$membersList.innerHTML += ` 55 |
  • 56 |

    ${item.user}

    57 |
    58 | 59 | 63 |
    64 | | 65 |
  • 66 | `; 67 | }); 68 | document.querySelectorAll('.todo-item').forEach(item =>{ 69 | item.addEventListener('click', this.handleEditOrDelete.bind(this)); 70 | }); 71 | } 72 | } 73 | 74 | window.addEventListener('DOMContentLoaded', async () =>{ 75 | const signup = new Signup(); 76 | await signup.init(); 77 | }); 78 | -------------------------------------------------------------------------------- /community-website/public/js/socketio/socket.io.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Socket.IO v2.3.0 3 | * (c) 2014-2019 Guillermo Rauch 4 | * Released under the MIT License. 5 | */ 6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){function r(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var n,r=o(t),i=r.source,u=r.id,p=r.path,h=c[u]&&p in c[u].nsps,f=e.forceNew||e["force new connection"]||!1===e.multiplex||h;return f?(a("ignoring socket cache for %s",i),n=s(i,e)):(c[u]||(a("new io instance for %s",i),c[u]=s(i,e)),n=c[u]),r.query&&!e.query&&(e.query=r.query),n.socket(r.path,e)}var o=n(1),i=n(7),s=n(15),a=n(3)("socket.io-client");t.exports=e=r;var c=e.managers={};e.protocol=i.protocol,e.connect=r,e.Manager=n(15),e.Socket=n(39)},function(t,e,n){function r(t,e){var n=t;e=e||"undefined"!=typeof location&&location,null==t&&(t=e.protocol+"//"+e.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?e.protocol+t:e.host+t),/^(https?|wss?):\/\//.test(t)||(i("protocol-less url %s",t),t="undefined"!=typeof e?e.protocol+"//"+t:"https://"+t),i("parse %s",t),n=o(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";var r=n.host.indexOf(":")!==-1,s=r?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+s+":"+n.port,n.href=n.protocol+"://"+s+(e&&e.port===n.port?"":":"+n.port),n}var o=n(2),i=n(3)("socket.io-client:url");t.exports=r},function(t,e){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,r=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=n.exec(t||""),a={},c=14;c--;)a[r[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e,n){(function(r){"use strict";function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(e){if(e[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+e[0]+(this.useColors?"%c ":" ")+"+"+t.exports.humanize(this.diff),this.useColors){var n="color: "+this.color;e.splice(1,0,n,"color: inherit");var r=0,o=0;e[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(r++,"%c"===t&&(o=r))}),e.splice(o,0,n)}}function s(){var t;return"object"===("undefined"==typeof console?"undefined":p(console))&&console.log&&(t=console).log.apply(t,arguments)}function a(t){try{t?e.storage.setItem("debug",t):e.storage.removeItem("debug")}catch(n){}}function c(){var t=void 0;try{t=e.storage.getItem("debug")}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function u(){try{return localStorage}catch(t){}}var p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage=u(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.exports=n(5)(e);var h=t.exports.formatters;h.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}).call(e,n(4))},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(p===setTimeout)return setTimeout(t,0);if((p===n||!p)&&setTimeout)return p=setTimeout,setTimeout(t,0);try{return p(t,0)}catch(e){try{return p.call(null,t,0)}catch(e){return p.call(this,t,0)}}}function i(t){if(h===clearTimeout)return clearTimeout(t);if((h===r||!h)&&clearTimeout)return h=clearTimeout,clearTimeout(t);try{return h(t)}catch(e){try{return h.call(null,t)}catch(e){return h.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):m=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++m1)for(var n=1;n100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*h;case"weeks":case"week":case"w":return n*p;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){var e=Math.abs(t);return e>=u?Math.round(t/u)+"d":e>=c?Math.round(t/c)+"h":e>=a?Math.round(t/a)+"m":e>=s?Math.round(t/s)+"s":t+"ms"}function o(t){var e=Math.abs(t);return e>=u?i(t,e,u,"day"):e>=c?i(t,e,c,"hour"):e>=a?i(t,e,a,"minute"):e>=s?i(t,e,s,"second"):t+" ms"}function i(t,e,n,r){var o=e>=1.5*n;return Math.round(t/n)+" "+r+(o?"s":"")}var s=1e3,a=60*s,c=60*a,u=24*c,p=7*u,h=365.25*u;t.exports=function(t,e){e=e||{};var i=typeof t;if("string"===i&&t.length>0)return n(t);if("number"===i&&isFinite(t))return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(){}function o(t){var n=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(n+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(n+=t.nsp+","),null!=t.id&&(n+=t.id),null!=t.data){var r=i(t.data);if(r===!1)return g;n+=r}return f("encoded %j as %s",t,n),n}function i(t){try{return JSON.stringify(t)}catch(e){return!1}}function s(t,e){function n(t){var n=d.deconstructPacket(t),r=o(n.packet),i=n.buffers;i.unshift(r),e(i)}d.removeBlobs(t,n)}function a(){this.reconstructor=null}function c(t){var n=0,r={type:Number(t.charAt(0))};if(null==e.types[r.type])return h("unknown packet type "+r.type);if(e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type){for(var o="";"-"!==t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!==t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"===t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","===i)break;if(r.nsp+=i,n===t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n===t.length)break}r.id=Number(r.id)}if(t.charAt(++n)){var a=u(t.substr(n)),c=a!==!1&&(r.type===e.ERROR||y(a));if(!c)return h("invalid payload");r.data=a}return f("decoded %s as %j",t,r),r}function u(t){try{return JSON.parse(t)}catch(e){return!1}}function p(t){this.reconPack=t,this.buffers=[]}function h(t){return{type:e.ERROR,data:"parser error: "+t}}var f=n(8)("socket.io-parser"),l=n(11),d=n(12),y=n(13),m=n(14);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=r,e.Decoder=a;var g=e.ERROR+'"encode error"';r.prototype.encode=function(t,n){if(f("encoding packet %j",t),e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,n);else{var r=o(t);n([r])}},l(a.prototype),a.prototype.add=function(t){var n;if("string"==typeof t)n=c(t),e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type?(this.reconstructor=new p(n),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",n)):this.emit("decoded",n);else{if(!m(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");n=this.reconstructor.takeBinaryData(t),n&&(this.reconstructor=null,this.emit("decoded",n))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},p.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},p.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,n){(function(r){"use strict";function o(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function i(t){var n=this.useColors;if(t[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+t[0]+(n?"%c ":" ")+"+"+e.humanize(this.diff),n){var r="color: "+this.color;t.splice(1,0,r,"color: inherit");var o=0,i=0;t[0].replace(/%[a-zA-Z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,r)}}function s(){return"object"===("undefined"==typeof console?"undefined":p(console))&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(n){}}function c(){var t;try{t=e.storage.debug}catch(n){}return!t&&"undefined"!=typeof r&&"env"in r&&(t=r.env.DEBUG),t}function u(){try{return window.localStorage}catch(t){}}var p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e=t.exports=n(9),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),e.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},e.enable(c())}).call(e,n(4))},function(t,e,n){"use strict";function r(t){var n,r=0;for(n in t)r=(r<<5)-r+t.charCodeAt(n),r|=0;return e.colors[Math.abs(r)%e.colors.length]}function o(t){function n(){if(n.enabled){var t=n,r=+new Date,i=r-(o||r);t.diff=i,t.prev=o,t.curr=r,o=r;for(var s=new Array(arguments.length),a=0;a100)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),r=(e[2]||"ms").toLowerCase();switch(r){case"years":case"year":case"yrs":case"yr":case"y":return n*p;case"days":case"day":case"d":return n*u;case"hours":case"hour":case"hrs":case"hr":case"h":return n*c;case"minutes":case"minute":case"mins":case"min":case"m":return n*a;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,n){if(!(t0)return n(t);if("number"===i&&isNaN(t)===!1)return e["long"]?o(t):r(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,n){function r(t){if(t)return o(t)}function o(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},r.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var r,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},r.prototype.cleanup=function(){p("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();p("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var n=setTimeout(function(){t.skipReconnect||(p("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(p("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(p("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(n)}})}},r.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,n){t.exports=n(17),t.exports.parser=n(24)},function(t,e,n){function r(t,e){return this instanceof r?(e=e||{},t&&"object"==typeof t&&(e=t,t=null),t?(t=p(t),e.hostname=t.host,e.secure="https"===t.protocol||"wss"===t.protocol,e.port=t.port,t.query&&(e.query=t.query)):e.host&&(e.hostname=p(e.host).host),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.agent=e.agent||!1,this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?443:80),this.query=e.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==e.upgrade,this.path=(e.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!e.forceJSONP,this.jsonp=!1!==e.jsonp,this.forceBase64=!!e.forceBase64,this.enablesXDR=!!e.enablesXDR,this.withCredentials=!1!==e.withCredentials,this.timestampParam=e.timestampParam||"t",this.timestampRequests=e.timestampRequests,this.transports=e.transports||["polling","websocket"],this.transportOptions=e.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=e.policyPort||843,this.rememberUpgrade=e.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=e.onlyBinaryUpgrades,this.perMessageDeflate=!1!==e.perMessageDeflate&&(e.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=e.pfx||null,this.key=e.key||null,this.passphrase=e.passphrase||null,this.cert=e.cert||null,this.ca=e.ca||null,this.ciphers=e.ciphers||null,this.rejectUnauthorized=void 0===e.rejectUnauthorized||e.rejectUnauthorized,this.forceNode=!!e.forceNode,this.isReactNative="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),("undefined"==typeof self||this.isReactNative)&&(e.extraHeaders&&Object.keys(e.extraHeaders).length>0&&(this.extraHeaders=e.extraHeaders),e.localAddress&&(this.localAddress=e.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,void this.open()):new r(t,e)}function o(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}var i=n(18),s=n(11),a=n(3)("engine.io-client:socket"),c=n(38),u=n(24),p=n(2),h=n(32);t.exports=r,r.priorWebsocketSuccess=!1,s(r.prototype),r.protocol=u.protocol,r.Socket=r,r.Transport=n(23),r.transports=n(18),r.parser=n(24),r.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=u.protocol,e.transport=t;var n=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var r=new i[t]({query:e,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,withCredentials:n.withCredentials||this.withCredentials,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0,isReactNative:this.isReactNative});return r},r.prototype.open=function(){var t;if(this.rememberUpgrade&&r.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{ 7 | if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(n){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},r.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},r.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;h=h||e}h||(a('probe transport "%s" opened',t),p.send([{type:"ping",data:"probe"}]),p.once("packet",function(e){if(!h)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",p),!p)return;r.priorWebsocketSuccess="websocket"===p.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){h||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),u(),f.setTransport(p),p.send([{type:"upgrade"}]),f.emit("upgrade",p),p=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var n=new Error("probe error");n.transport=p.name,f.emit("upgradeError",n)}}))}function n(){h||(h=!0,u(),p.close(),p=null)}function o(e){var r=new Error("probe error: "+e);r.transport=p.name,n(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",r)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){p&&t.name!==p.name&&(a('"%s" works - aborting "%s"',t.name,p.name),n())}function u(){p.removeListener("open",e),p.removeListener("error",o),p.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var p=this.createTransport(t,{probe:1}),h=!1,f=this;r.priorWebsocketSuccess=!1,p.once("open",e),p.once("error",o),p.once("close",i),this.once("close",s),this.once("upgrading",c),p.open()},r.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",r.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:C}var i=new Uint8Array(t),o=i[0],s=f(t,1);return w&&"blob"===n&&(s=new w([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var n=b[t.charAt(0)];if(!u)return{type:n,data:{base64:!0,data:t.substr(1)}};var r=u.decode(t.substr(1));return"blob"===e&&w&&(r=new w([r])),{type:n,data:r}},e.encodePayload=function(t,n,r){function o(t){return t.length+":"+t}function i(t,r){e.encodePacket(t,!!s&&n,!1,function(t){r(null,o(t))})}"function"==typeof n&&(r=n,n=null);var s=h(t);return n&&s?w&&!g?e.encodePayloadAsBlob(t,r):e.encodePayloadAsArrayBuffer(t,r):t.length?void c(t,i,function(t,e){return r(e.join(""))}):r("0:")},e.decodePayload=function(t,n,r){if("string"!=typeof t)return e.decodePayloadAsBinary(t,n,r);"function"==typeof n&&(r=n,n=null);var o;if(""===t)return r(C,0,1);for(var i,s,a="",c=0,u=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",u=1;255!==s[u];u++){if(c.length>310)return r(C,0,1);c+=s[u]}o=f(o,2+c.length),c=parseInt(c);var p=f(o,0,c);if(a)try{p=String.fromCharCode.apply(null,new Uint8Array(p))}catch(h){var l=new Uint8Array(p);p="";for(var u=0;ur&&(n=r),e>=r||e>=n||0===r)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(n-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=d(e>>>10&1023|55296),e=56320|1023&e),o+=d(e);return o}function o(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function i(t,e){return d(t>>e&63|128)}function s(t,e){if(0==(4294967168&t))return d(t);var n="";return 0==(4294965248&t)?n=d(t>>6&31|192):0==(4294901760&t)?(o(t,e)||(t=65533),n=d(t>>12&15|224),n+=i(t,6)):0==(4292870144&t)&&(n=d(t>>18&7|240),n+=i(t,12),n+=i(t,6)),n+=d(63&t|128)}function a(t,e){e=e||{};for(var r,o=!1!==e.strict,i=n(t),a=i.length,c=-1,u="";++c=f)throw Error("Invalid byte index");var t=255&h[l];if(l++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function u(t){var e,n,r,i,s;if(l>f)throw Error("Invalid byte index");if(l==f)return!1;if(e=255&h[l],l++,0==(128&e))return e;if(192==(224&e)){if(n=c(),s=(31&e)<<6|n,s>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&e)){if(n=c(),r=c(),s=(15&e)<<12|n<<6|r,s>=2048)return o(s,t)?s:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(n=c(),r=c(),i=c(),s=(7&e)<<18|n<<12|r<<6|i,s>=65536&&s<=1114111))return s;throw Error("Invalid UTF-8 detected")}function p(t,e){e=e||{};var o=!1!==e.strict;h=n(t),f=h.length,l=0;for(var i,s=[];(i=u(o))!==!1;)s.push(i);return r(s)}/*! https://mths.be/utf8js v2.1.2 by @mathias */ 8 | var h,f,l,d=String.fromCharCode;t.exports={version:"2.1.2",encode:a,decode:p}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],i+=t[(3&r[n])<<4|r[n+1]>>4],i+=t[(15&r[n+1])<<2|r[n+2]>>6],i+=t[63&r[n+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,r,o,i,s,a=.75*t.length,c=t.length,u=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var p=new ArrayBuffer(a),h=new Uint8Array(p);for(e=0;e>4,h[u++]=(15&o)<<4|i>>2,h[u++]=(3&i)<<6|63&s;return p}}()},function(t,e){function n(t){return t.map(function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var n=new Uint8Array(t.byteLength);n.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=n.buffer}return e}return t})}function r(t,e){e=e||{};var r=new i;return n(t).forEach(function(t){r.append(t)}),e.type?r.getBlob(e.type):r.getBlob()}function o(t,e){return new Blob(n(t),e||{})}var i="undefined"!=typeof i?i:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(e){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(e){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;"undefined"!=typeof Blob&&(r.prototype=Blob.prototype,o.prototype=Blob.prototype),t.exports=function(){return s?a?Blob:o:c?r:void 0}()},function(t,e){e.encode=function(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e.length&&(e+="&"),e+=encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e},e.decode=function(t){for(var e={},n=t.split("&"),r=0,o=n.length;r0);return e}function r(t){var e=0;for(p=0;p';i=document.createElement(e)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),c=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=c,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),r(),t=t.replace(p,"\\\n"),this.area.value=t.replace(u,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&n()}:this.iframe.onload=n}}).call(e,function(){return this}())},function(t,e,n){function r(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=o&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(l=i),s.call(this,t)}var o,i,s=n(23),a=n(24),c=n(32),u=n(33),p=n(34),h=n(3)("engine.io-client:websocket");if("undefined"!=typeof WebSocket?o=WebSocket:"undefined"!=typeof self&&(o=self.WebSocket||self.MozWebSocket),"undefined"==typeof window)try{i=n(37)}catch(f){}var l=o||i;t.exports=r,u(r,s),r.prototype.name="websocket",r.prototype.supportsBinary=!0,r.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket&&!this.isReactNative?e?new l(t,e):new l(t):new l(t,e,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},r.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},r.prototype.write=function(t){function e(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit("drain")},0)}var n=this;this.writable=!1;for(var r=t.length,o=0,i=r;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=n,n.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(t){this.ms=t},n.prototype.setMax=function(t){this.max=t},n.prototype.setJitter=function(t){this.jitter=t}}])}); 9 | //# sourceMappingURL=socket.io.js.map 10 | -------------------------------------------------------------------------------- /community-website/public/js/todos.js: -------------------------------------------------------------------------------- 1 | class Todos { 2 | constructor() { 3 | this.baseurl = '/api/v1/todos'; 4 | this.todos = []; 5 | this.$todos = document.querySelector('.todo-list'); 6 | this.$form = document.querySelector('.todo-form') 7 | } 8 | 9 | async init() { 10 | await this.updateTodos(); 11 | this.$form.addEventListener('submit', async evt => { 12 | evt.preventDefault(); 13 | await this.createTodo(); 14 | }); 15 | } 16 | 17 | async getTodos() { 18 | let data = await fetch (this.baseurl); 19 | data = await data.json(); 20 | this.todos = data; 21 | let incomplete =[]; 22 | let complete =[]; 23 | let toDoList=[]; 24 | 25 | this.todos.forEach((item)=>{ 26 | if(item.status=="incomplete"){incomplete.push(item)} 27 | if(item.status=="complete"){complete.push(item)} 28 | }) 29 | 30 | if(incomplete.length!=0){ 31 | incomplete.sort((a,b)=>{ 32 | return a.date.localeCompare(b.date); 33 | }); 34 | for(let i = 0; i < incomplete.length;i++){ 35 | toDoList.push(incomplete[i]) 36 | } 37 | } 38 | 39 | if(complete.length!=0){ 40 | console.log(complete) 41 | complete.sort((a,b)=>{ 42 | return a.date.localeCompare(b.date); 43 | }); 44 | for(let i = 0; i < complete.length;i++){ 45 | toDoList.push(complete[i]) 46 | } 47 | } 48 | 49 | this.todos=toDoList; 50 | console.log(this.todos, "getTodos") 51 | await this.renderTodos(); 52 | } 53 | 54 | async createTodo(){ 55 | try { 56 | const newData = { 57 | todo: this.$form.todo.value, 58 | status: 'incomplete', 59 | date: new Date().toLocaleString() 60 | }; 61 | 62 | const options = { 63 | method: 'POST', 64 | headers: { 65 | Accept: 'application/json', 66 | 'Content-Type': 'application/json' 67 | }, 68 | body: JSON.stringify(newData) 69 | }; 70 | let data = await fetch(this.baseurl,options); 71 | data = await data.json(); 72 | await this.updateTodos(); 73 | } catch(error){ 74 | console.error(error); 75 | } 76 | } 77 | 78 | async updateTodo(id, newData) { 79 | try { 80 | const options = { 81 | method: 'PUT', 82 | headers: { 83 | Accept: 'application/json', 84 | 'Content-Type': 'application/json' 85 | }, 86 | body: JSON.stringify(newData) 87 | }; 88 | let data = await fetch(this.baseurl + `/${id}`, options); 89 | data = await data.json(); 90 | await this.updateTodos(); 91 | } catch (error) { 92 | console.error(error); 93 | } 94 | } 95 | 96 | async deleteTodo(id) { 97 | try { 98 | const options = { 99 | method: 'DELETE' 100 | }; 101 | let data = await fetch(this.baseurl + `/${id}`, options); 102 | data = await data.json(); 103 | this.updateTodos(); 104 | } catch (error) { 105 | console.error(error); 106 | } 107 | } 108 | 109 | async updateTodos() { 110 | await this.getTodos(); 111 | this.renderTodos(); 112 | } 113 | 114 | renderTodos() { 115 | this.$todos.innerHTML =''; 116 | this.todos.forEach(item =>{ 117 | this.$todos.innerHTML += ` 118 |
  • 119 |

    ${item.user}

    120 |

    ${item.date}

    121 |
    122 | 123 | ${item.status} 127 |
    128 | | 129 |
  • 130 | `; 131 | }); 132 | document.querySelectorAll('.todo-item').forEach(item =>{ 133 | item.addEventListener('click', this.handleEditOrDelete.bind(this)); 134 | }); 135 | // document.querySelectorAll('.todo-toggle').forEach(item =>{ 136 | // item.addEventListener('click', this.toggleStatus.bind(this)); 137 | // }); 138 | } 139 | 140 | async toggleStatus(evt){ 141 | let itemId = evt.target.parentElement.parentElement.parentElement.id 142 | let listItem = evt.target.parentElement.parentElement; 143 | let status; 144 | if (evt.target.checked){ 145 | status="complete"; 146 | } else { 147 | status="incomplete"; 148 | } 149 | console.log(evt.target.checked) 150 | const updateData = { 151 | todo: listItem.children[0].value, 152 | status: status, 153 | user: listItem.children[0].innerHTML, 154 | date: listItem.children[1].innerHTML, 155 | }; 156 | 157 | await this.updateTodo(itemId, updateData); 158 | } 159 | async handleEditOrDelete(evt) { 160 | { 161 | const $clickedButton = evt.target; 162 | const $listItem = evt.currentTarget; 163 | 164 | if($clickedButton.classList.contains('todo-item__delete')){ 165 | await this.deleteTodo($listItem.id); 166 | console.log('delete', $listItem, $listItem.id); 167 | } else if ($clickedButton.classList.contains('todo-item__edit')) { 168 | const form = $listItem.children[2]; 169 | // console.log(form, "form") 170 | // console.log($listItem.children[0].innerHTML, "list") 171 | let status; 172 | if (form.children[1].children[0].checked){ 173 | status="complete"; 174 | } else { 175 | status="incomplete"; 176 | } 177 | const updateData = { 178 | todo: form.todo.value, 179 | status: status, 180 | user: $listItem.children[0].innerHTML, 181 | date: $listItem.children[1].innerHTML, 182 | }; 183 | // console.log(updateData) 184 | await this.updateTodo($listItem.id, updateData); 185 | } 186 | } 187 | } 188 | } 189 | 190 | window.addEventListener('DOMContentLoaded', async () =>{ 191 | const todos = new Todos(); 192 | await todos.init(); 193 | }); 194 | -------------------------------------------------------------------------------- /community-website/public/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: #DDECF0; 4 | } 5 | 6 | nav { 7 | padding: 15px; 8 | background-color: #3C9AB2; 9 | } 10 | 11 | .container { 12 | margin: auto; 13 | max-width: 800px; 14 | } 15 | 16 | .header { 17 | text-align: center; 18 | } 19 | 20 | .signup-form, .login-form { 21 | text-align: center; 22 | line-height: 2; 23 | } 24 | 25 | .signup-form input, .login-form input { 26 | width: 50%; 27 | } 28 | 29 | p { 30 | font-color: #343434; 31 | } 32 | 33 | input[type=text]::placeholder, input[type=password]::placeholder { 34 | /* Firefox, Chrome, Opera */ 35 | text-align: center; 36 | } 37 | 38 | .nav-links { 39 | display: flex; 40 | } 41 | 42 | li:last-child { 43 | justify-content: flex-end; 44 | } 45 | 46 | .nav-links > li { 47 | flex: 1; 48 | display: flex; 49 | } 50 | 51 | .nav-links a { 52 | color: white; 53 | text-decoration: none; 54 | } 55 | 56 | .nav-item > a:hover { 57 | color: black; 58 | } 59 | 60 | .app { 61 | max-width: 1000px; 62 | margin: auto; 63 | } 64 | 65 | .divTable { 66 | display: table; 67 | width: 100%; 68 | } 69 | 70 | .divTableRow { 71 | display: table-row; 72 | } 73 | 74 | .divTableHeading { 75 | background-color: #EEE; 76 | display: table-header-group; 77 | } 78 | 79 | .divTableCell, .divTableHead { 80 | border: 1px solid #999999; 81 | display: table-cell; 82 | padding: 3px 10px; 83 | } 84 | 85 | .divTableHeading { 86 | background-color: #EEE; 87 | display: table-header-group; 88 | font-weight: bold; 89 | } 90 | 91 | .divTableFoot { 92 | background-color: #EEE; 93 | display: table-footer-group; 94 | font-weight: bold; 95 | } 96 | 97 | .divTableBody { 98 | display: table-row-group; 99 | } 100 | 101 | .todo-list { 102 | display: grid; 103 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 104 | grid-gap: 25px; 105 | list-style: none; 106 | } 107 | 108 | .main { 109 | margin: 25px; 110 | } 111 | 112 | #app { 113 | max-width: 1000px; 114 | margin: auto; 115 | margin-top: 125px; 116 | } 117 | 118 | textarea { 119 | width: 100%; 120 | height: 150px; 121 | padding: 12px 20px; 122 | box-sizing: border-box; 123 | border: 2px solid #ccc; 124 | border-radius: 4px; 125 | background-color: #F9EFD6; 126 | resize: none; 127 | } 128 | 129 | .todo-date { 130 | font-size: 12px; 131 | } 132 | 133 | .switch { 134 | position: relative; 135 | display: inline-block; 136 | width: 60px; 137 | height: 34px; 138 | } 139 | 140 | .switch input { 141 | opacity: 0; 142 | width: 0; 143 | height: 0; 144 | } 145 | 146 | .slider { 147 | position: absolute; 148 | cursor: pointer; 149 | top: 0; 150 | left: 0; 151 | right: 0; 152 | bottom: 0; 153 | background-color: #F22300; 154 | -webkit-transition: 0.4s; 155 | transition: 0.4s; 156 | } 157 | 158 | .slider:before { 159 | position: absolute; 160 | content: ""; 161 | height: 26px; 162 | width: 26px; 163 | left: 4px; 164 | bottom: 4px; 165 | background-color: white; 166 | -webkit-transition: 0.4s; 167 | transition: 0.4s; 168 | } 169 | 170 | input:checked + .slider { 171 | background-color: #00A08A; 172 | } 173 | 174 | input:focus + .slider { 175 | box-shadow: 0 0 1px #2196F3; 176 | } 177 | 178 | input:checked + .slider:before { 179 | -webkit-transform: translateX(26px); 180 | -ms-transform: translateX(26px); 181 | transform: translateX(26px); 182 | } 183 | 184 | /* Rounded sliders */ 185 | .slider.round { 186 | border-radius: 34px; 187 | } 188 | 189 | .slider.round:before { 190 | border-radius: 50%; 191 | } 192 | 193 | button { 194 | width: 100px; 195 | height: 40px; 196 | border-radius: 0; 197 | color: gray; 198 | background-color: white; 199 | } 200 | 201 | button:hover { 202 | background-color: grey; 203 | color: white; 204 | } 205 | 206 | .switch { 207 | position: relative; 208 | display: inline-block; 209 | width: 60px; 210 | height: 20px; 211 | margin: 15px; 212 | } 213 | 214 | .slider::before { 215 | position: absolute; 216 | content: ""; 217 | height: 20px; 218 | width: 20px; 219 | left: 4px; 220 | bottom: 0px; 221 | background-color: white; 222 | -webkit-transition: 0.4s; 223 | transition: 0.4s; 224 | } 225 | 226 | .submit-button { 227 | height: 40px; 228 | border-radius: 0; 229 | color: gray; 230 | background-color: white; 231 | } 232 | 233 | .login-page { 234 | display: flex; 235 | } 236 | 237 | .login-form { 238 | display: 1; 239 | } 240 | 241 | .info { 242 | margin-left: 35px; 243 | display: 1; 244 | } 245 | 246 | /*# sourceMappingURL=main.css.map */ 247 | -------------------------------------------------------------------------------- /community-website/sass/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background-color: #DDECF0; 4 | } 5 | 6 | nav { 7 | padding: 15px; 8 | background-color: #3C9AB2; 9 | } 10 | 11 | .container { 12 | margin: auto; 13 | max-width: 800px; 14 | } 15 | 16 | .header { 17 | text-align: center; 18 | } 19 | 20 | .signup-form, .login-form { 21 | text-align: center; 22 | line-height: 2; 23 | } 24 | 25 | .signup-form input, .login-form input { 26 | width: 50%; 27 | } 28 | 29 | p { 30 | font-color: #343434; 31 | } 32 | 33 | input[type=text]::placeholder, input[type=password]::placeholder { 34 | /* Firefox, Chrome, Opera */ 35 | text-align: center; 36 | } 37 | 38 | .nav-links { 39 | display: flex; 40 | } 41 | 42 | li:last-child { 43 | justify-content: flex-end; 44 | } 45 | 46 | .nav-links > li { 47 | flex: 1; 48 | display: flex; 49 | } 50 | 51 | .nav-links a { 52 | color: white; 53 | text-decoration: none; 54 | } 55 | 56 | .nav-item > a:hover { 57 | color: black; 58 | } 59 | 60 | .app { 61 | max-width: 1000px; 62 | margin: auto; 63 | } 64 | 65 | .divTable { 66 | display: table; 67 | width: 100%; 68 | } 69 | 70 | .divTableRow { 71 | display: table-row; 72 | } 73 | 74 | .divTableHeading { 75 | background-color: #EEE; 76 | display: table-header-group; 77 | } 78 | 79 | .divTableCell, .divTableHead { 80 | border: 1px solid #999999; 81 | display: table-cell; 82 | padding: 3px 10px; 83 | } 84 | 85 | .divTableHeading { 86 | background-color: #EEE; 87 | display: table-header-group; 88 | font-weight: bold; 89 | } 90 | 91 | .divTableFoot { 92 | background-color: #EEE; 93 | display: table-footer-group; 94 | font-weight: bold; 95 | } 96 | 97 | .divTableBody { 98 | display: table-row-group; 99 | } 100 | 101 | .todo-list { 102 | display: grid; 103 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 104 | grid-gap: 25px; 105 | list-style: none; 106 | } 107 | 108 | .main { 109 | margin: 25px; 110 | } 111 | 112 | #app { 113 | max-width: 1000px; 114 | margin: auto; 115 | margin-top: 125px; 116 | } 117 | 118 | textarea { 119 | width: 100%; 120 | height: 150px; 121 | padding: 12px 20px; 122 | box-sizing: border-box; 123 | border: 2px solid #ccc; 124 | border-radius: 4px; 125 | background-color: #F9EFD6; 126 | resize: none; 127 | } 128 | 129 | .todo-date { 130 | font-size: 12px; 131 | } 132 | 133 | .switch { 134 | position: relative; 135 | display: inline-block; 136 | width: 60px; 137 | height: 34px; 138 | } 139 | 140 | .switch input { 141 | opacity: 0; 142 | width: 0; 143 | height: 0; 144 | } 145 | 146 | .slider { 147 | position: absolute; 148 | cursor: pointer; 149 | top: 0; 150 | left: 0; 151 | right: 0; 152 | bottom: 0; 153 | background-color: #F22300; 154 | -webkit-transition: 0.4s; 155 | transition: 0.4s; 156 | } 157 | 158 | .slider:before { 159 | position: absolute; 160 | content: ""; 161 | height: 26px; 162 | width: 26px; 163 | left: 4px; 164 | bottom: 4px; 165 | background-color: white; 166 | -webkit-transition: 0.4s; 167 | transition: 0.4s; 168 | } 169 | 170 | input:checked + .slider { 171 | background-color: #00A08A; 172 | } 173 | 174 | input:focus + .slider { 175 | box-shadow: 0 0 1px #2196F3; 176 | } 177 | 178 | input:checked + .slider:before { 179 | -webkit-transform: translateX(26px); 180 | -ms-transform: translateX(26px); 181 | transform: translateX(26px); 182 | } 183 | 184 | /* Rounded sliders */ 185 | .slider.round { 186 | border-radius: 34px; 187 | } 188 | 189 | .slider.round:before { 190 | border-radius: 50%; 191 | } 192 | 193 | button { 194 | width: 100px; 195 | height: 40px; 196 | border-radius: 0; 197 | color: gray; 198 | background-color: white; 199 | } 200 | 201 | button:hover { 202 | background-color: grey; 203 | color: white; 204 | } 205 | 206 | .switch { 207 | position: relative; 208 | display: inline-block; 209 | width: 60px; 210 | height: 20px; 211 | margin: 15px; 212 | } 213 | 214 | .slider::before { 215 | position: absolute; 216 | content: ""; 217 | height: 20px; 218 | width: 20px; 219 | left: 4px; 220 | bottom: 0px; 221 | background-color: white; 222 | -webkit-transition: 0.4s; 223 | transition: 0.4s; 224 | } 225 | 226 | .submit-button { 227 | height: 40px; 228 | border-radius: 0; 229 | color: gray; 230 | background-color: white; 231 | } 232 | 233 | .login-page { 234 | display: flex; 235 | } 236 | 237 | .login-form { 238 | display: 1; 239 | } 240 | 241 | .info { 242 | margin-left: 35px; 243 | display: 1; 244 | } 245 | 246 | /*# sourceMappingURL=main.css.map */ 247 | -------------------------------------------------------------------------------- /community-website/sass/main.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAEA;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE,YA/Bc;;;AAsChB;AACE;EACA;;;AAGF;EACE;;;AAGF;EACI;;;AAEJ;EACI;EACA;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACC;EACA;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACI;EACA;EACA;EACA;;;AAGJ;EACE;;;AAGF;EACE;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;AACA;EACE;;;AAGF;EACE;;;AAGF;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACI;EACA;EACA;EACA;;;AAGJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA","file":"main.css"} -------------------------------------------------------------------------------- /community-website/sass/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: auto; 3 | width: 800px; 4 | } 5 | 6 | .header { 7 | text-align: center; 8 | } 9 | 10 | .login-form { 11 | text-align: center; 12 | } 13 | 14 | p { 15 | font-color: #343434; 16 | } 17 | 18 | /*# sourceMappingURL=style.css.map */ 19 | -------------------------------------------------------------------------------- /community-website/sass/style.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAEA;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,YAhBc","file":"style.css"} -------------------------------------------------------------------------------- /community-website/sass/style.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #343434; 2 | 3 | body { 4 | margin: 0; 5 | background-color: #DDECF0; 6 | } 7 | 8 | nav { 9 | padding: 15px; 10 | background-color: #3C9AB2; 11 | } 12 | 13 | .container { 14 | margin: auto; 15 | max-width: 800px; 16 | } 17 | 18 | .header{ 19 | text-align: center; 20 | } 21 | 22 | .signup-form, .login-form { 23 | text-align: center; 24 | line-height: 2; 25 | } 26 | 27 | .signup-form input, .login-form input{ 28 | width: 50%; 29 | } 30 | 31 | p { 32 | font-color: $primary-color; 33 | } 34 | 35 | .todo-item{ 36 | // border: 2px solid black; 37 | } 38 | 39 | input[type="text"]::placeholder, input[type="password"]::placeholder { 40 | /* Firefox, Chrome, Opera */ 41 | text-align: center; 42 | } 43 | 44 | .nav-links { 45 | display:flex 46 | } 47 | 48 | li:last-child { 49 | justify-content: flex-end; 50 | } 51 | .nav-links > li { 52 | flex: 1; 53 | display: flex; 54 | } 55 | 56 | .nav-links a { 57 | color: white; 58 | text-decoration:none; 59 | } 60 | 61 | .nav-item > a:hover { 62 | color: black; 63 | } 64 | 65 | .app { 66 | max-width:1000px; 67 | margin:auto; 68 | } 69 | 70 | .divTable{ 71 | display: table; 72 | width: 100%; 73 | } 74 | .divTableRow { 75 | display: table-row; 76 | } 77 | .divTableHeading { 78 | background-color: #EEE; 79 | display: table-header-group; 80 | } 81 | .divTableCell, .divTableHead { 82 | border: 1px solid #999999; 83 | display: table-cell; 84 | padding: 3px 10px; 85 | } 86 | .divTableHeading { 87 | background-color: #EEE; 88 | display: table-header-group; 89 | font-weight: bold; 90 | } 91 | .divTableFoot { 92 | background-color: #EEE; 93 | display: table-footer-group; 94 | font-weight: bold; 95 | } 96 | .divTableBody { 97 | display: table-row-group; 98 | } 99 | .todo-list { 100 | display: grid; 101 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 102 | grid-gap: 25px; 103 | list-style: none; 104 | } 105 | 106 | .main { 107 | margin:25px; 108 | } 109 | 110 | #app { 111 | max-width:1000px; 112 | margin:auto; 113 | margin-top:125px; 114 | } 115 | 116 | textarea { 117 | width: 100%; 118 | height: 150px; 119 | padding: 12px 20px; 120 | box-sizing: border-box; 121 | border: 2px solid #ccc; 122 | border-radius: 4px; 123 | background-color:#F9EFD6; 124 | resize: none; 125 | } 126 | 127 | .todo-date { 128 | font-size: 12px; 129 | } 130 | 131 | 132 | .switch { 133 | position: relative; 134 | display: inline-block; 135 | width: 60px; 136 | height: 34px; 137 | } 138 | 139 | .switch input { 140 | opacity: 0; 141 | width: 0; 142 | height: 0; 143 | } 144 | 145 | .slider { 146 | position: absolute; 147 | cursor: pointer; 148 | top: 0; 149 | left: 0; 150 | right: 0; 151 | bottom: 0; 152 | background-color: #F22300; 153 | -webkit-transition: .4s; 154 | transition: .4s; 155 | } 156 | 157 | .slider:before { 158 | position: absolute; 159 | content: ""; 160 | height: 26px; 161 | width: 26px; 162 | left: 4px; 163 | bottom: 4px; 164 | background-color: white; 165 | -webkit-transition: .4s; 166 | transition: .4s; 167 | } 168 | 169 | input:checked + .slider { 170 | background-color: #00A08A; 171 | } 172 | 173 | input:focus + .slider { 174 | box-shadow: 0 0 1px #2196F3; 175 | } 176 | 177 | input:checked + .slider:before { 178 | -webkit-transform: translateX(26px); 179 | -ms-transform: translateX(26px); 180 | transform: translateX(26px); 181 | } 182 | 183 | /* Rounded sliders */ 184 | .slider.round { 185 | border-radius: 34px; 186 | } 187 | 188 | .slider.round:before { 189 | border-radius: 50%; 190 | } 191 | 192 | button { 193 | width: 100px; 194 | height: 40px; 195 | border-radius: 0; 196 | color: gray; 197 | background-color: white; 198 | } 199 | 200 | button:hover{ 201 | background-color:grey; 202 | color:white; 203 | } 204 | 205 | 206 | .switch { 207 | position: relative; 208 | display: inline-block; 209 | width: 60px; 210 | height: 20px; 211 | margin: 15px; 212 | } 213 | 214 | .slider::before { 215 | position: absolute; 216 | content: ""; 217 | height: 20px; 218 | width: 20px; 219 | left: 4px; 220 | bottom: 0px; 221 | background-color: white; 222 | -webkit-transition: 0.4s; 223 | transition: 0.4s; 224 | } 225 | 226 | .submit-button { 227 | height: 40px; 228 | border-radius: 0; 229 | color: gray; 230 | background-color: white; 231 | } 232 | 233 | .login-page { 234 | display: flex; 235 | } 236 | 237 | .login-form { 238 | display: 1; 239 | } 240 | 241 | .info { 242 | margin-left:35px; 243 | display:1; 244 | } 245 | -------------------------------------------------------------------------------- /community-website/server.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | path = require('path'), 3 | express = require('express'), 4 | app = express(), 5 | uuidV1 = require('uuid/v1'), 6 | cookieParser = require('cookie-parser'), 7 | bodyParser = require('body-parser'), 8 | server = require('http').createServer(app), 9 | io = require('socket.io')(server); 10 | 11 | let clientList = []; 12 | let clientListStore = [] 13 | 14 | const PORT = 80; 15 | const publicURL = path.resolve(`${__dirname}/public`); 16 | 17 | app.use(express.static(publicURL)); 18 | 19 | const Datastore = require('nedb'); 20 | const db = new Datastore({filename: 'members', autoload: true, onload: (err)=>{if (err) throw err}}); 21 | const session = require('express-session'); 22 | const nedbstore = require('nedb-session-store')(session); 23 | 24 | const todo_db = new Datastore({filename: 'todos', autoload: true}); 25 | 26 | app.use(express.json()); 27 | app.use(cookieParser()); 28 | 29 | app.use( 30 | session( 31 | { 32 | secret: 'secret', 33 | cookie: { 34 | maxAge: 365 * 24 * 60 * 1000 35 | }, 36 | store: new nedbstore({ 37 | filename: 'sessions' 38 | }), 39 | resave: true, 40 | saveUninitialized: true 41 | } 42 | ) 43 | ); 44 | 45 | const urlencodedParser = bodyParser.urlencoded({ extended: true }); 46 | app.use(urlencodedParser) 47 | 48 | app.get("/", (req, res)=>{ 49 | if(!req.session.username){ 50 | res.render(path.resolve(__dirname + "/views/login.ejs")); 51 | } else { 52 | res.render(path.resolve(__dirname + "/views/index.ejs"), req); 53 | } 54 | }); 55 | 56 | app.get('/signup', (req, res)=>{ 57 | res.render(path.resolve(__dirname + "/views/signup.ejs")) 58 | }); 59 | 60 | app.get("/members", (req, res)=>{ 61 | if(!req.session.username){ 62 | res.render(path.resolve(__dirname + "/views/login.ejs")); 63 | } else { 64 | res.render(path.resolve(__dirname + "/views/members.ejs"), req); 65 | } 66 | }); 67 | 68 | app.get("/tasks", (req, res)=>{ 69 | if(!req.session.username){ 70 | res.render(path.resolve(__dirname + "/views/login.ejs")); 71 | } else { 72 | res.render(path.resolve(__dirname + "/views/tasks.ejs"), req); 73 | } 74 | }); 75 | 76 | app.get('/chat', function(req, res) { 77 | if(!req.session.username){ 78 | res.render(path.resolve(__dirname + "/views/login.ejs")); 79 | } else { 80 | res.render(path.resolve(__dirname + "/views/chat.ejs"), req) 81 | } 82 | }); 83 | 84 | app.get('/profile', function(req, res) { 85 | if(!req.session.username){ 86 | res.render(path.resolve(__dirname + "/views/login.ejs")); 87 | } else { 88 | res.render(path.resolve(__dirname + "/views/profile.ejs"), req) 89 | } 90 | }); 91 | 92 | app.get('/configuration', function(req, res){ 93 | res.render(path.resolve(__dirname+"/views/configuration.ejs")) 94 | }) 95 | 96 | app.get('/logout', (req, res) =>{ 97 | delete req.session.username; 98 | res.redirect('/'); 99 | }) 100 | 101 | app.post('/login', (req, res)=>{ 102 | let found = false; 103 | db.find({"username": req.body.username, "password": req.body.password}, (err, docs)=>{ 104 | if (docs.length == 0){ 105 | console.log(`${req.body.username} login failed.`) 106 | res.send("No no no. No auth. Try Again") 107 | } else { 108 | let userRecord = docs[0] 109 | console.log(`${req.body.username} login succeeded.`) 110 | req.session.username = userRecord.username; 111 | req.session.name = userRecord.name; 112 | req.session.lastlogin = Date.now(); 113 | res.render(__dirname + '/views/index.ejs', req) 114 | } 115 | }) 116 | }) 117 | 118 | server.listen(PORT, ()=>{ 119 | console.log(`Listening on ${PORT}`) 120 | }) 121 | 122 | app.get("/api/v1/members", async (req, res) => { 123 | try { 124 | 125 | db.find({}, (err, docs)=>{ 126 | if (err) throw err; 127 | res.json(docs) 128 | }) 129 | } catch(error) { 130 | console.error(error); 131 | res.json(error); 132 | } 133 | }); 134 | 135 | app.post("/api/v1/members", async (req, res) => { 136 | try { 137 | const member = { 138 | username: req.body.username, 139 | name: req.body.name, 140 | id: req.body.id, 141 | password: req.body.password 142 | } 143 | 144 | let isNewUser = false; 145 | db.find({ "username":member.username }, (err, docs)=>{ 146 | if(docs.length===0){ 147 | isNewUser = true; 148 | let goodPassword = checkPassword(member.password); 149 | console.log(isNewUser, goodPassword) 150 | if(isNewUser && goodPassword){ 151 | db.insert(member, (err, doc)=>{ 152 | if (err) throw err; 153 | console.log(doc) 154 | }) 155 | res.json(member); 156 | 157 | } 158 | } else { 159 | console.log("User exists!") 160 | } 161 | }) 162 | } catch (error) { 163 | console.error(error); 164 | res.json(error); 165 | } 166 | }); 167 | 168 | app.put("/api/v1/members/:id", async (req, res) => { 169 | try { 170 | const update = { 171 | username: req.body.username, 172 | name: req.body.name, 173 | id: req.body.id, 174 | password: req.body.password 175 | } 176 | 177 | db.update({_id: req.params.id}, update, {}, (err)=>{ 178 | if (err) throw err; 179 | console.log(update) 180 | }) 181 | res.json(update) 182 | } catch (error) { 183 | console.error(error); 184 | res.json(update); 185 | } 186 | }); 187 | 188 | app.delete("/api/v1/members/:id", async (req,res) =>{ 189 | try { 190 | const options = { 191 | multi: false 192 | }; 193 | 194 | db.remove({_id: req.params.id}, options, (err, numRemoved)=>{ 195 | if (err) throw err; 196 | console.log(numRemoved); 197 | }) 198 | res.json({"Deleted":req.params.id}) 199 | } catch(error){ 200 | console.log(error); 201 | res.json(error); 202 | } 203 | }); 204 | 205 | app.get("/api/v1/todos", async (req, res) => { 206 | try { 207 | todo_db.find({}, (err, docs)=>{ 208 | if (err) throw err; 209 | res.json(docs) 210 | }) 211 | } catch(error) { 212 | console.error(error); 213 | res.json(error); 214 | } 215 | }); 216 | 217 | app.post("/api/v1/todos", async (req, res) => { 218 | try { 219 | const todo = { 220 | todo: req.body.todo, 221 | status: req.body.status, 222 | user: req.session.username, 223 | date: req.body.date 224 | }; 225 | todo_db.insert(todo, (err, doc)=>{ 226 | if (err) throw err; 227 | console.log(doc) 228 | }) 229 | res.json(todo); 230 | } catch (error) { 231 | console.error(error); 232 | res.json(error); 233 | } 234 | }); 235 | 236 | app.put("/api/v1/todos/:id", async (req, res) => { 237 | try { 238 | const update = { 239 | todo: req.body.todo, 240 | status: req.body.status, 241 | user: req.body.user, 242 | date: req.body.date, 243 | } 244 | 245 | todo_db.update({_id: req.params.id}, update, {}, (err)=>{ 246 | if (err) throw err; 247 | console.log(update) 248 | }) 249 | res.json(update) 250 | } catch (error) { 251 | console.error(error); 252 | res.json(update); 253 | } 254 | }); 255 | 256 | app.delete("/api/v1/todos/:id", async (req,res) =>{ 257 | try { 258 | const options = { 259 | multi: false 260 | }; 261 | 262 | todo_db.remove({_id: req.params.id}, options, (err, numRemoved)=>{ 263 | if (err) throw err; 264 | console.log(numRemoved); 265 | }) 266 | res.json({"Deleted":req.params.id}) 267 | } catch(error){ 268 | console.log(error); 269 | res.json(error); 270 | } 271 | }); 272 | 273 | function checkPassword(password){ 274 | if(password.length > 2 && password.length < 13){ 275 | return true 276 | } else { 277 | console.log("bad pass") 278 | return false 279 | } 280 | } 281 | 282 | io.on('connection', (socket) => { 283 | console.log('Connection was made!') 284 | var address ; 285 | 286 | console.log(session.username, "sessi") 287 | socket.on('send name', (name)=>{ 288 | console.log(name, "name") 289 | address=name; 290 | let internalList = {"name": name, "id":socket.id} 291 | clientList.push(address); 292 | clientListStore.push(internalList) 293 | clientList = [...new Set(clientList)]; 294 | clientListStore = [...new Set(clientListStore)]; 295 | io.emit('server response', `${clientList}`); 296 | }) 297 | //io.emit('server response', `${address}`) 298 | 299 | 300 | console.log("We have a new client: " + socket.id); 301 | // When this user emits, client side: socket.emit('otherevent',some data); 302 | socket.on('chatmessage', function (data) { 303 | // Data comes in as whatever was sent, including objects 304 | console.log("Received: 'chatmessage' " + data); 305 | console.log("what is the data: ", data) 306 | 307 | // Send it to all of the clients 308 | socket.broadcast.emit('chatmessage', data); 309 | }) 310 | 311 | socket.on('disconnect', () =>{ 312 | clientList = [...new Set(clientList)]; 313 | clientListStore = [...new Set(clientListStore)]; 314 | console.log(clientListStore) 315 | // let index = clientListStore.indexOf(socket.id); 316 | let index = findWithAttr(clientListStore, 'id', socket.id) 317 | console.log(clientListStore, index, "index"); 318 | if(index > -1){ 319 | let newList =[] 320 | clientListStore.splice(index, 1); 321 | clientListStore.forEach((client)=>{ 322 | newList.push(client.name) 323 | }) 324 | io.emit('server response', `${newList}`); 325 | } 326 | console.log(`${socket.id} disconnected.`) 327 | }); 328 | }); 329 | 330 | 331 | function findWithAttr(array, attr, value) { 332 | for(var i = 0; i < array.length; i += 1) { 333 | if(array[i][attr] === value) { 334 | return i; 335 | } 336 | } 337 | return -1; 338 | } 339 | 340 | -------------------------------------------------------------------------------- /community-website/sessions: -------------------------------------------------------------------------------- 1 | {"_id":"75L0MIoKgUnJ6RlsY_cbL868CQMCeQ-a","session":{"cookie":{"originalMaxAge":525600000,"expires":{"$$date":1589767268982},"httpOnly":true,"path":"/"}},"expiresAt":{"$$date":1589767268982},"createdAt":{"$$date":1589240760385},"updatedAt":{"$$date":1589241668983}} 2 | {"_id":"75L0MIoKgUnJ6RlsY_cbL868CQMCeQ-a","session":{"cookie":{"originalMaxAge":525600000,"expires":{"$$date":1589767278993},"httpOnly":true,"path":"/"}},"expiresAt":{"$$date":1589767278993},"createdAt":{"$$date":1589240760385},"updatedAt":{"$$date":1589241678996}} 3 | {"_id":"75L0MIoKgUnJ6RlsY_cbL868CQMCeQ-a","session":{"cookie":{"originalMaxAge":525600000,"expires":{"$$date":1589767279350},"httpOnly":true,"path":"/"}},"expiresAt":{"$$date":1589767279350},"createdAt":{"$$date":1589240760385},"updatedAt":{"$$date":1589241679350}} 4 | {"_id":"75L0MIoKgUnJ6RlsY_cbL868CQMCeQ-a","session":{"cookie":{"originalMaxAge":525600000,"expires":{"$$date":1589767287966},"httpOnly":true,"path":"/"}},"expiresAt":{"$$date":1589767287966},"createdAt":{"$$date":1589240760385},"updatedAt":{"$$date":1589241687967}} 5 | {"_id":"75L0MIoKgUnJ6RlsY_cbL868CQMCeQ-a","session":{"cookie":{"originalMaxAge":525600000,"expires":{"$$date":1589767294664},"httpOnly":true,"path":"/"}},"expiresAt":{"$$date":1589767294664},"createdAt":{"$$date":1589240760385},"updatedAt":{"$$date":1589241694664}} 6 | -------------------------------------------------------------------------------- /community-website/todos: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emceelamb/bridge/99ee90cc8a41e3538661e430da1e66f7328cb4d2/community-website/todos -------------------------------------------------------------------------------- /community-website/views/chat.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Community Portal Chat 5 | 6 | 7 | 8 | 99 | 100 | 101 | 102 | 103 | 104 | 116 |
    117 |

    Local Chat

    118 |
    119 |
    120 |
    121 |
    122 |
    123 |
    124 |

    Peers

    125 |
      126 |
      127 |
      128 |
      129 |

      Network Chat

      130 |
      131 | 132 | 138 |
      139 |
      140 |
      141 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /community-website/views/configuration.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Community Portal 6 | 7 | 8 | 9 | 22 | 23 | 24 |
      25 |
      26 | 27 |

      Web Cache Setup

      28 |

      To make use of Squid HTTP Web Cache you must configure your client devices to use the router as the HTTP Proxy. This enables Squid Cache to act as a proxy server allowing all connected to use the network cache server which will allow faster web requests for connected clients. It is optimal to configure the proxy in your system settings so that all applications running your device. The specific instructions will vary between operating systems. 29 |

      30 |

      Chrome/ Chromium Proxy Setup

      31 |

      32 | Chromium uses your system proxy settings. When changed it will affect all applications on your machine. 33 |

      34 |
        35 |
      1. 36 | Open Chrome and open Preferences panel 37 |
      2. 38 |
      3. 39 | Navigate to Advanced > Systems page 40 | 41 |
      4. 42 |
      5. 43 | Manually configure proxy with the router IP address as your proxy server 44 |
        45 | - HTTP Proxy: `10.0.0.1` 46 |
        47 | - Port: `3128` 48 |
        49 | 50 | 51 |
      6. 52 |
      7. 53 | You can test the proxy server by running: curl -x 10.0.0.1:3128 google.com and if it returns the page your proxy works! 54 |
      8. 55 |
      9. 56 | You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 57 | 58 | 59 |
      10. 60 |
      61 | 62 |

      Fire Fox Proxy Setup

      63 | 64 |

      65 | You can manually set the router as the proxy server on Firefox Browser. You will do this in the browser's preferences page. 66 |

      67 | 68 | 69 |
        70 |
      1. 71 | Open Firefox Browser and go to Preferences panel 72 |
      2. 73 |
      3. 74 | Navigate to Network Settings and select *settings* 75 |
      4. 76 |
      5. 77 | Select Manual Proxy Configuration and set: 78 |
        79 | 80 | - HTTP Proxy: `10.0.0.1` 81 |
        82 | 83 | - Port: `3128` 84 |
      6. 85 |
      7. 86 | You can test the proxy server by running: curl -x 10.0.0.1:3128 google.com and if it returns the page your proxy works! 87 |
      8. 88 |
      9. 89 | You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 90 |
        91 | 92 |
      10. 93 |
      94 | 95 |

      Iphone

      96 |
        97 |
      1. 98 | Navigate WiFi Settings 99 | 100 |
        101 | 102 | 103 |
      2. 104 |
      3. 105 | Select to Bridge WiFi options 106 |
        107 | 108 | 109 |
      4. 110 |
      5. 111 | Select Configure Proxy 112 |
      6. 113 |
      7. 114 | Enable Manual Proxy 115 |
        116 | 117 | 118 | - Server: `10.0.0.1` 119 |
        120 | 121 | - Port: `3128` 122 |
      8. 123 |
      9. 124 | You can test the proxy server by running: curl -x 10.0.0.1:3128 google.com and if it returns the page your proxy works! 125 |
      10. 126 |
      11. 127 | You should be now be sending requests to the Squid Proxy Server! This will speed up your internet as devices on the network will cache http here. 128 |
      12. 129 |
      130 |
      131 |
      132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /community-website/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 21 |
      22 |
      23 |

      Community Portal

      24 |
      25 |
      26 |

      Task List

      27 |
      28 |
      29 |
      30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /community-website/views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Community Portal 6 | 7 | 8 | 9 | 22 | 23 | 24 |
      25 |
      26 | 50 |
      51 |
      52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /community-website/views/members.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 |
      24 |
      25 |

      Community Members

      26 |
      27 |
      28 |
      29 |
      30 |
      31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /community-website/views/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Profile 6 | 7 | 8 | 9 | 10 | 13 | 14 | 24 |
      25 |
      26 |

      <%= session.name %> Tasks

      27 |
      28 |
      29 |

      Add task

      30 |
      31 | 32 | 33 |
      34 |

      Task List

      35 |
      36 |
      37 |
      38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /community-website/views/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 |
      21 |
      22 |
      23 |
      24 | Bridge logo 25 |
      26 |

      Signup

      27 |
      28 |
      29 |

      30 |

      40 |

      41 |
      42 |
      43 |
      44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /community-website/views/tasks.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 |
      23 |
      24 |

      Community Tasks

      25 |
      26 |
      27 |

      Task List

      28 |
      29 |

      Add tasks

      30 |
      31 | 32 |
      33 | 34 |
      35 |
      36 |
      37 | 38 | 39 | 40 | --------------------------------------------------------------------------------