├── .github ├── scripts │ ├── install-arduino-core-esp32.sh │ ├── install-arduino-core-esp8266.sh │ ├── install-arduino-ide.sh │ ├── install-platformio.sh │ └── on-push.sh ├── stale.yml └── workflows │ └── push.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── README.md ├── _config.yml ├── component.mk ├── examples ├── CaptivePortal │ └── CaptivePortal.ino ├── ESP_AsyncFSBrowser │ ├── ESP_AsyncFSBrowser.ino │ └── data │ │ ├── .exclude.files │ │ ├── ace.ico.gz │ │ ├── acefull.js.gz │ │ ├── edit_gz │ │ ├── favicon.ico │ │ ├── folder │ │ ├── image.jpg │ │ └── test.txt │ │ ├── index.htm │ │ ├── worker-css.js.gz │ │ ├── worker-html.js.gz │ │ ├── worker-javascript.js.gz │ │ └── worker-json.js.gz ├── SmartSwitch │ ├── 1.PNG │ ├── 2.PNG │ ├── 3.PNG │ ├── 4.PNG │ ├── ESPAsyncWiFiManager.cpp │ ├── ESPAsyncWiFiManager.h │ ├── PinOut_Notes.txt │ ├── README.md │ ├── SmartSwitch.ino │ ├── Xtea.cpp │ ├── Xtea.h │ ├── data │ │ ├── .exclude.files │ │ ├── ace.ico.gz │ │ ├── acefull.js.gz │ │ ├── app.css.gz │ │ ├── app.min.js.gz │ │ ├── edit_gz │ │ ├── favicon.ico.gz │ │ ├── index.htm │ │ ├── login │ │ │ ├── favicon.ico.gz │ │ │ └── index.htm │ │ ├── worker-css.js.gz │ │ ├── worker-html.js.gz │ │ ├── worker-javascript.js.gz │ │ └── worker-json.js.gz │ └── data_src │ │ ├── .exclude.files │ │ ├── acefull.js │ │ ├── app.css │ │ ├── app.min.js │ │ ├── favicon.ico │ │ ├── index.htm │ │ └── js_css_src │ │ ├── .exclude.files │ │ ├── ace-drafts │ │ ├── ace.js │ │ ├── ext-searchbox.js │ │ ├── mode-css.js │ │ ├── mode-html.js │ │ ├── mode-javascript.js │ │ └── worker-html.js │ │ ├── ace.js │ │ ├── app.js │ │ ├── js-time-picker │ │ ├── 1-inline.html │ │ ├── 2-popup.html │ │ ├── README.txt │ │ ├── tpick-light-pop.css │ │ ├── tpick-light.css │ │ ├── tpick-pop.js │ │ └── tpick.js │ │ ├── justgage-1.2.2 │ │ ├── examples │ │ │ ├── auto-adjust.htm │ │ │ ├── counter.html │ │ │ ├── custom-interval.htm │ │ │ ├── custom-node.html │ │ │ ├── custom-sectors.html │ │ │ ├── custom-value-renderer.html │ │ │ ├── customize-style.htm │ │ │ ├── defaults.html │ │ │ ├── font-options.html │ │ │ ├── format-number.html │ │ │ ├── html5-data-attribute-config.html │ │ │ ├── human-friendly-numbers.html │ │ │ ├── pointer.html │ │ │ ├── refresh-maximum.html │ │ │ ├── responsive-gauges.htm │ │ │ └── reverse.html │ │ ├── justgage.js │ │ └── raphael-2.1.4.min.js │ │ └── styled-notifications │ │ ├── .babelrc │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE.md │ │ ├── __tests__ │ │ ├── helpers.test.js │ │ └── index.tests.js │ │ ├── demo │ │ ├── demo.js │ │ ├── index.html │ │ └── index_my.html │ │ ├── dist │ │ ├── notifications.css │ │ ├── notifications.css.gz │ │ ├── notifications.js │ │ └── notifications.js.gz │ │ ├── package.json │ │ ├── readme.md │ │ ├── src │ │ ├── helpers.js │ │ ├── index.js │ │ ├── polyfills │ │ │ └── classList.js │ │ └── style.scss │ │ └── webpack.config.js ├── regex_patterns │ ├── .test.build_flags │ └── regex_patterns.ino └── simple_server │ └── simple_server.ino ├── extras ├── README.md ├── do_ed_fs.bat ├── do_emb.bat ├── ehg.c ├── ehg.exe ├── rehg.c ├── rehg.exe ├── tmp1 │ └── placeholder ├── undo.bat └── update_ace.bat ├── keywords.txt ├── library.json ├── library.properties └── src ├── AsyncEventSource.cpp ├── AsyncEventSource.h ├── AsyncJson.h ├── AsyncWebSocket.cpp ├── AsyncWebSocket.h ├── AsyncWebSynchronization.h ├── ESPAsyncWebServer.h ├── SPIFFSEditor.cpp ├── SPIFFSEditor.h ├── StringArray.h ├── WebAuthentication.cpp ├── WebAuthentication.h ├── WebHandlerImpl.h ├── WebHandlers.cpp ├── WebRequest.cpp ├── WebResponseImpl.h ├── WebResponses.cpp ├── WebServer.cpp ├── edit.htm └── edit.htm.gz.h /.github/scripts/install-arduino-core-esp32.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export ARDUINO_ESP32_PATH="$ARDUINO_USR_PATH/hardware/espressif/esp32" 4 | if [ ! -d "$ARDUINO_ESP32_PATH" ]; then 5 | echo "Installing ESP32 Arduino Core ..." 6 | script_init_path="$PWD" 7 | mkdir -p "$ARDUINO_USR_PATH/hardware/espressif" 8 | cd "$ARDUINO_USR_PATH/hardware/espressif" 9 | 10 | echo "Installing Python Serial ..." 11 | pip install pyserial > /dev/null 12 | 13 | if [ "$OS_IS_WINDOWS" == "1" ]; then 14 | echo "Installing Python Requests ..." 15 | pip install requests > /dev/null 16 | fi 17 | 18 | if [ "$GITHUB_REPOSITORY" == "espressif/arduino-esp32" ]; then 19 | echo "Linking Core..." 20 | ln -s $GITHUB_WORKSPACE esp32 21 | else 22 | echo "Cloning Core Repository..." 23 | git clone https://github.com/espressif/arduino-esp32.git esp32 > /dev/null 2>&1 24 | fi 25 | 26 | echo "Updating Submodules ..." 27 | cd esp32 28 | git submodule update --init --recursive > /dev/null 2>&1 29 | 30 | echo "Installing Platform Tools ..." 31 | cd tools && python get.py 32 | cd $script_init_path 33 | 34 | echo "ESP32 Arduino has been installed in '$ARDUINO_ESP32_PATH'" 35 | echo "" 36 | fi 37 | -------------------------------------------------------------------------------- /.github/scripts/install-arduino-core-esp8266.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Installing ESP8266 Arduino Core ..." 4 | script_init_path="$PWD" 5 | mkdir -p "$ARDUINO_USR_PATH/hardware/esp8266com" 6 | cd "$ARDUINO_USR_PATH/hardware/esp8266com" 7 | 8 | echo "Installing Python Serial ..." 9 | pip install pyserial > /dev/null 10 | 11 | if [ "$OS_IS_WINDOWS" == "1" ]; then 12 | echo "Installing Python Requests ..." 13 | pip install requests > /dev/null 14 | fi 15 | 16 | echo "Cloning Core Repository ..." 17 | git clone https://github.com/esp8266/Arduino.git esp8266 > /dev/null 2>&1 18 | 19 | echo "Updating submodules ..." 20 | cd esp8266 21 | git submodule update --init --recursive > /dev/null 2>&1 22 | 23 | echo "Installing Platform Tools ..." 24 | cd tools 25 | python get.py > /dev/null 26 | cd $script_init_path 27 | 28 | echo "ESP8266 Arduino has been installed in '$ARDUINO_USR_PATH/hardware/esp8266com'" 29 | echo "" 30 | -------------------------------------------------------------------------------- /.github/scripts/install-arduino-ide.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64 4 | #OSTYPE: 'msys', ARCH: 'x86_64' => win32 5 | #OSTYPE: 'darwin18', ARCH: 'i386' => macos 6 | 7 | OSBITS=`arch` 8 | if [[ "$OSTYPE" == "linux"* ]]; then 9 | export OS_IS_LINUX="1" 10 | ARCHIVE_FORMAT="tar.xz" 11 | if [[ "$OSBITS" == "i686" ]]; then 12 | OS_NAME="linux32" 13 | elif [[ "$OSBITS" == "x86_64" ]]; then 14 | OS_NAME="linux64" 15 | elif [[ "$OSBITS" == "armv7l" || "$OSBITS" == "aarch64" ]]; then 16 | OS_NAME="linuxarm" 17 | else 18 | OS_NAME="$OSTYPE-$OSBITS" 19 | echo "Unknown OS '$OS_NAME'" 20 | exit 1 21 | fi 22 | elif [[ "$OSTYPE" == "darwin"* ]]; then 23 | export OS_IS_MACOS="1" 24 | ARCHIVE_FORMAT="zip" 25 | OS_NAME="macosx" 26 | elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then 27 | export OS_IS_WINDOWS="1" 28 | ARCHIVE_FORMAT="zip" 29 | OS_NAME="windows" 30 | else 31 | OS_NAME="$OSTYPE-$OSBITS" 32 | echo "Unknown OS '$OS_NAME'" 33 | exit 1 34 | fi 35 | export OS_NAME 36 | 37 | ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp" 38 | ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" 39 | 40 | if [ "$OS_IS_MACOS" == "1" ]; then 41 | export ARDUINO_IDE_PATH="/Applications/Arduino.app/Contents/Java" 42 | export ARDUINO_USR_PATH="$HOME/Documents/Arduino" 43 | elif [ "$OS_IS_WINDOWS" == "1" ]; then 44 | export ARDUINO_IDE_PATH="$HOME/arduino_ide" 45 | export ARDUINO_USR_PATH="$HOME/Documents/Arduino" 46 | else 47 | export ARDUINO_IDE_PATH="$HOME/arduino_ide" 48 | export ARDUINO_USR_PATH="$HOME/Arduino" 49 | fi 50 | 51 | if [ ! -d "$ARDUINO_IDE_PATH" ]; then 52 | echo "Installing Arduino IDE on $OS_NAME ..." 53 | echo "Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ..." 54 | if [ "$OS_IS_LINUX" == "1" ]; then 55 | wget -O "arduino.$ARCHIVE_FORMAT" "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 56 | echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." 57 | tar xf "arduino.$ARCHIVE_FORMAT" > /dev/null 58 | mv arduino-nightly "$ARDUINO_IDE_PATH" 59 | else 60 | curl -o "arduino.$ARCHIVE_FORMAT" -L "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 61 | echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." 62 | unzip "arduino.$ARCHIVE_FORMAT" > /dev/null 63 | if [ "$OS_IS_MACOS" == "1" ]; then 64 | mv "Arduino.app" "/Applications/Arduino.app" 65 | else 66 | mv arduino-nightly "$ARDUINO_IDE_PATH" 67 | fi 68 | fi 69 | rm -rf "arduino.$ARCHIVE_FORMAT" 70 | 71 | mkdir -p "$ARDUINO_USR_PATH/libraries" 72 | mkdir -p "$ARDUINO_USR_PATH/hardware" 73 | 74 | echo "Arduino IDE Installed in '$ARDUINO_IDE_PATH'" 75 | echo "" 76 | fi 77 | 78 | function build_sketch(){ # build_sketch [extra-options] 79 | if [ "$#" -lt 2 ]; then 80 | echo "ERROR: Illegal number of parameters" 81 | echo "USAGE: build_sketch [extra-options]" 82 | return 1 83 | fi 84 | 85 | local fqbn="$1" 86 | local sketch="$2" 87 | local build_flags="$3" 88 | local xtra_opts="$4" 89 | local win_opts="" 90 | if [ "$OS_IS_WINDOWS" == "1" ]; then 91 | local ctags_version=`ls "$ARDUINO_IDE_PATH/tools-builder/ctags/"` 92 | local preprocessor_version=`ls "$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/"` 93 | win_opts="-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version" 94 | fi 95 | 96 | echo "" 97 | echo "Compiling '"$(basename "$sketch")"' ..." 98 | mkdir -p "$ARDUINO_BUILD_DIR" 99 | mkdir -p "$ARDUINO_CACHE_DIR" 100 | $ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \ 101 | -fqbn=$fqbn \ 102 | -warnings="all" \ 103 | -tools "$ARDUINO_IDE_PATH/tools-builder" \ 104 | -tools "$ARDUINO_IDE_PATH/tools" \ 105 | -built-in-libraries "$ARDUINO_IDE_PATH/libraries" \ 106 | -hardware "$ARDUINO_IDE_PATH/hardware" \ 107 | -hardware "$ARDUINO_USR_PATH/hardware" \ 108 | -libraries "$ARDUINO_USR_PATH/libraries" \ 109 | -build-cache "$ARDUINO_CACHE_DIR" \ 110 | -build-path "$ARDUINO_BUILD_DIR" \ 111 | -prefs=compiler.cpp.extra_flags="$build_flags" \ 112 | $win_opts $xtra_opts "$sketch" 113 | } 114 | 115 | function count_sketches() # count_sketches 116 | { 117 | local examples="$1" 118 | rm -rf sketches.txt 119 | if [ ! -d "$examples" ]; then 120 | touch sketches.txt 121 | return 0 122 | fi 123 | local sketches=$(find $examples -name *.ino) 124 | local sketchnum=0 125 | for sketch in $sketches; do 126 | local sketchdir=$(dirname $sketch) 127 | local sketchdirname=$(basename $sketchdir) 128 | local sketchname=$(basename $sketch) 129 | if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then 130 | continue 131 | fi; 132 | if [[ -f "$sketchdir/.test.skip" ]]; then 133 | continue 134 | fi 135 | echo $sketch >> sketches.txt 136 | sketchnum=$(($sketchnum + 1)) 137 | done 138 | return $sketchnum 139 | } 140 | 141 | function build_sketches() # build_sketches [extra-options] 142 | { 143 | local fqbn=$1 144 | local examples=$2 145 | local chunk_idex=$3 146 | local chunks_num=$4 147 | local xtra_opts=$5 148 | 149 | if [ "$#" -lt 2 ]; then 150 | echo "ERROR: Illegal number of parameters" 151 | echo "USAGE: build_sketches [ ] [extra-options]" 152 | return 1 153 | fi 154 | 155 | if [ "$#" -lt 4 ]; then 156 | chunk_idex="0" 157 | chunks_num="1" 158 | xtra_opts=$3 159 | fi 160 | 161 | if [ "$chunks_num" -le 0 ]; then 162 | echo "ERROR: Chunks count must be positive number" 163 | return 1 164 | fi 165 | if [ "$chunk_idex" -ge "$chunks_num" ]; then 166 | echo "ERROR: Chunk index must be less than chunks count" 167 | return 1 168 | fi 169 | 170 | set +e 171 | count_sketches "$examples" 172 | local sketchcount=$? 173 | set -e 174 | local sketches=$(cat sketches.txt) 175 | rm -rf sketches.txt 176 | 177 | local chunk_size=$(( $sketchcount / $chunks_num )) 178 | local all_chunks=$(( $chunks_num * $chunk_size )) 179 | if [ "$all_chunks" -lt "$sketchcount" ]; then 180 | chunk_size=$(( $chunk_size + 1 )) 181 | fi 182 | 183 | local start_index=$(( $chunk_idex * $chunk_size )) 184 | if [ "$sketchcount" -le "$start_index" ]; then 185 | echo "Skipping job" 186 | return 0 187 | fi 188 | 189 | local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) 190 | if [ "$end_index" -gt "$sketchcount" ]; then 191 | end_index=$sketchcount 192 | fi 193 | 194 | local start_num=$(( $start_index + 1 )) 195 | echo "Found $sketchcount Sketches"; 196 | echo "Chunk Count : $chunks_num" 197 | echo "Chunk Size : $chunk_size" 198 | echo "Start Sketch: $start_num" 199 | echo "End Sketch : $end_index" 200 | 201 | local sketchnum=0 202 | for sketch in $sketches; do 203 | local sketchdir=$(dirname $sketch) 204 | local sketchdirname=$(basename $sketchdir) 205 | local sketchname=$(basename $sketch) 206 | if [ "${sketchdirname}.ino" != "$sketchname" ] \ 207 | || [ -f "$sketchdir/.test.skip" ]; then 208 | continue 209 | fi 210 | sketchnum=$(($sketchnum + 1)) 211 | if [ "$sketchnum" -le "$start_index" ] \ 212 | || [ "$sketchnum" -gt "$end_index" ]; then 213 | continue 214 | fi 215 | local sketchBuildFlags="" 216 | if [ -f "$sketchdir/.test.build_flags" ]; then 217 | while read line; do 218 | sketchBuildFlags="$sketchBuildFlags $line" 219 | done < "$sketchdir/.test.build_flags" 220 | fi 221 | build_sketch "$fqbn" "$sketch" "$sketchBuildFlags" "$xtra_opts" 222 | local result=$? 223 | if [ $result -ne 0 ]; then 224 | return $result 225 | fi 226 | done 227 | return 0 228 | } 229 | -------------------------------------------------------------------------------- /.github/scripts/install-platformio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Installing Python Wheel ..." 4 | pip install wheel > /dev/null 2>&1 5 | 6 | echo "Installing PlatformIO ..." 7 | pip install -U platformio > /dev/null 2>&1 8 | 9 | echo "PlatformIO has been installed" 10 | echo "" 11 | 12 | 13 | function build_pio_sketch(){ # build_pio_sketch 14 | if [ "$#" -lt 3 ]; then 15 | echo "ERROR: Illegal number of parameters" 16 | echo "USAGE: build_pio_sketch " 17 | return 1 18 | fi 19 | 20 | local board="$1" 21 | local sketch="$2" 22 | local buildFlags="$3" 23 | local sketch_dir=$(dirname "$sketch") 24 | echo "" 25 | echo "Compiling '"$(basename "$sketch")"' ..." 26 | python -m platformio ci -l '.' --board "$board" "$sketch_dir" --project-option="board_build.partitions = huge_app.csv" --project-option="build_flags=$buildFlags" 27 | } 28 | 29 | function count_sketches() # count_sketches 30 | { 31 | local examples="$1" 32 | rm -rf sketches.txt 33 | if [ ! -d "$examples" ]; then 34 | touch sketches.txt 35 | return 0 36 | fi 37 | local sketches=$(find $examples -name *.ino) 38 | local sketchnum=0 39 | for sketch in $sketches; do 40 | local sketchdir=$(dirname $sketch) 41 | local sketchdirname=$(basename $sketchdir) 42 | local sketchname=$(basename $sketch) 43 | if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then 44 | continue 45 | fi; 46 | if [[ -f "$sketchdir/.test.skip" ]]; then 47 | continue 48 | fi 49 | echo $sketch >> sketches.txt 50 | sketchnum=$(($sketchnum + 1)) 51 | done 52 | return $sketchnum 53 | } 54 | 55 | function build_pio_sketches() # build_pio_sketches 56 | { 57 | if [ "$#" -lt 2 ]; then 58 | echo "ERROR: Illegal number of parameters" 59 | echo "USAGE: build_pio_sketches [ ]" 60 | return 1 61 | fi 62 | 63 | local board=$1 64 | local examples=$2 65 | local chunk_idex=$3 66 | local chunks_num=$4 67 | 68 | if [ "$#" -lt 4 ]; then 69 | chunk_idex="0" 70 | chunks_num="1" 71 | fi 72 | 73 | if [ "$chunks_num" -le 0 ]; then 74 | echo "ERROR: Chunks count must be positive number" 75 | return 1 76 | fi 77 | if [ "$chunk_idex" -ge "$chunks_num" ]; then 78 | echo "ERROR: Chunk index must be less than chunks count" 79 | return 1 80 | fi 81 | 82 | set +e 83 | count_sketches "$examples" 84 | local sketchcount=$? 85 | set -e 86 | local sketches=$(cat sketches.txt) 87 | rm -rf sketches.txt 88 | 89 | local chunk_size=$(( $sketchcount / $chunks_num )) 90 | local all_chunks=$(( $chunks_num * $chunk_size )) 91 | if [ "$all_chunks" -lt "$sketchcount" ]; then 92 | chunk_size=$(( $chunk_size + 1 )) 93 | fi 94 | 95 | local start_index=$(( $chunk_idex * $chunk_size )) 96 | if [ "$sketchcount" -le "$start_index" ]; then 97 | echo "Skipping job" 98 | return 0 99 | fi 100 | 101 | local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) 102 | if [ "$end_index" -gt "$sketchcount" ]; then 103 | end_index=$sketchcount 104 | fi 105 | 106 | local start_num=$(( $start_index + 1 )) 107 | echo "Found $sketchcount Sketches"; 108 | echo "Chunk Count : $chunks_num" 109 | echo "Chunk Size : $chunk_size" 110 | echo "Start Sketch: $start_num" 111 | echo "End Sketch : $end_index" 112 | 113 | local sketchnum=0 114 | for sketch in $sketches; do 115 | local sketchdir=$(dirname $sketch) 116 | local sketchdirname=$(basename $sketchdir) 117 | local sketchname=$(basename $sketch) 118 | if [ "${sketchdirname}.ino" != "$sketchname" ] \ 119 | || [ -f "$sketchdir/.test.skip" ]; then 120 | continue 121 | fi 122 | local sketchBuildFlags="" 123 | if [ -f "$sketchdir/.test.build_flags" ]; then 124 | while read line; do 125 | sketchBuildFlags="$sketchBuildFlags $line" 126 | done < "$sketchdir/.test.build_flags" 127 | fi 128 | sketchnum=$(($sketchnum + 1)) 129 | if [ "$sketchnum" -le "$start_index" ] \ 130 | || [ "$sketchnum" -gt "$end_index" ]; then 131 | continue 132 | fi 133 | build_pio_sketch "$board" "$sketch" "$sketchBuildFlags" 134 | local result=$? 135 | if [ $result -ne 0 ]; then 136 | return $result 137 | fi 138 | done 139 | return 0 140 | } 141 | -------------------------------------------------------------------------------- /.github/scripts/on-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -z "$TRAVIS_BUILD_DIR" ]; then 6 | export GITHUB_WORKSPACE="$TRAVIS_BUILD_DIR" 7 | export GITHUB_REPOSITORY="$TRAVIS_REPO_SLUG" 8 | elif [ -z "$GITHUB_WORKSPACE" ]; then 9 | export GITHUB_WORKSPACE="$PWD" 10 | export GITHUB_REPOSITORY="lorol/ESPAsyncWebServer" 11 | fi 12 | 13 | TARGET_PLATFORM="$1" 14 | CHUNK_INDEX=$2 15 | CHUNKS_CNT=$3 16 | BUILD_PIO=0 17 | if [ "$#" -lt 1 ]; then 18 | TARGET_PLATFORM="esp32" 19 | fi 20 | if [ "$#" -lt 3 ] || [ "$CHUNKS_CNT" -le 0 ]; then 21 | CHUNK_INDEX=0 22 | CHUNKS_CNT=1 23 | elif [ "$CHUNK_INDEX" -gt "$CHUNKS_CNT" ]; then 24 | CHUNK_INDEX=$CHUNKS_CNT 25 | elif [ "$CHUNK_INDEX" -eq "$CHUNKS_CNT" ]; then 26 | BUILD_PIO=1 27 | fi 28 | 29 | if [ "$BUILD_PIO" -eq 0 ]; then 30 | # ArduinoIDE Test 31 | source ./.github/scripts/install-arduino-ide.sh 32 | 33 | echo "Installing ESPAsyncWebServer ..." 34 | cp -rf "$GITHUB_WORKSPACE" "$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer" 35 | echo "Installing ArduinoJson ..." 36 | git clone https://github.com/bblanchon/ArduinoJson "$ARDUINO_USR_PATH/libraries/ArduinoJson" > /dev/null 2>&1 37 | echo "Installing DHT sensor library ..." 38 | git clone https://github.com/adafruit/DHT-sensor-library "$ARDUINO_USR_PATH/libraries/DHT-sensor-library" > /dev/null 2>&1 39 | 40 | if [[ "$TARGET_PLATFORM" == "esp32" ]]; then 41 | echo "Installing LITTLEFS for ESP32 ..." 42 | git clone https://github.com/lorol/LITTLEFS "$ARDUINO_USR_PATH/libraries/LITTLEFS" > /dev/null 2>&1 43 | echo "Installing AsyncTCP ..." 44 | git clone https://github.com/me-no-dev/AsyncTCP "$ARDUINO_USR_PATH/libraries/AsyncTCP" > /dev/null 2>&1 45 | FQBN="espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app" 46 | source ./.github/scripts/install-arduino-core-esp32.sh 47 | echo "BUILDING ESP32 EXAMPLES" 48 | else 49 | echo "Installing ESPAsyncTCP ..." 50 | git clone https://github.com/me-no-dev/ESPAsyncTCP "$ARDUINO_USR_PATH/libraries/ESPAsyncTCP" > /dev/null 2>&1 51 | FQBN="esp8266com:esp8266:generic:eesz=4M1M,ip=lm2f" 52 | source ./.github/scripts/install-arduino-core-esp8266.sh 53 | echo "BUILDING ESP8266 EXAMPLES" 54 | fi 55 | build_sketches "$FQBN" "$GITHUB_WORKSPACE/examples" "$CHUNK_INDEX" "$CHUNKS_CNT" 56 | else 57 | # PlatformIO Test 58 | source ./.github/scripts/install-platformio.sh 59 | 60 | python -m platformio lib --storage-dir "$GITHUB_WORKSPACE" install 61 | echo "Installing ArduinoJson ..." 62 | python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git > /dev/null 2>&1 63 | if [[ "$TARGET_PLATFORM" == "esp32" ]]; then 64 | BOARD="esp32dev" 65 | echo "Installing AsyncTCP ..." 66 | python -m platformio lib -g install https://github.com/me-no-dev/AsyncTCP.git > /dev/null 2>&1 67 | echo "BUILDING ESP32 EXAMPLES" 68 | else 69 | BOARD="esp12e" 70 | echo "Installing ESPAsyncTCP ..." 71 | python -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncTCP.git > /dev/null 2>&1 72 | echo "BUILDING ESP8266 EXAMPLES" 73 | fi 74 | build_pio_sketches "$BOARD" "$GITHUB_WORKSPACE/examples" 75 | fi 76 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | daysUntilStale: 60 4 | daysUntilClose: 14 5 | limitPerRun: 30 6 | staleLabel: stale 7 | exemptLabels: 8 | - pinned 9 | - security 10 | - "to be implemented" 11 | - "for reference" 12 | - "move to PR" 13 | - "enhancement" 14 | 15 | only: issues 16 | onlyLabels: [] 17 | exemptProjects: false 18 | exemptMilestones: false 19 | exemptAssignees: false 20 | 21 | markComment: > 22 | [STALE_SET] This issue has been automatically marked as stale because it has not had 23 | recent activity. It will be closed in 14 days if no further activity occurs. Thank you 24 | for your contributions. 25 | 26 | unmarkComment: > 27 | [STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future. 28 | 29 | closeComment: > 30 | [STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: ESP Async Web Server CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release/* 8 | pull_request: 9 | 10 | jobs: 11 | 12 | build-arduino: 13 | name: Arduino for ${{ matrix.board }} on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | board: [esp32, esp8266] 19 | steps: 20 | - uses: actions/checkout@v1 21 | - name: Build Tests 22 | run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 0 1 23 | 24 | build-pio: 25 | name: PlatformIO for ${{ matrix.board }} on ${{ matrix.os }} 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [ubuntu-latest, windows-latest, macOS-latest] 30 | board: [esp32, esp8266] 31 | steps: 32 | - uses: actions/checkout@v1 33 | - name: Build Tests 34 | run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 1 1 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | 5 | os: 6 | - linux 7 | 8 | git: 9 | depth: false 10 | 11 | stages: 12 | - build 13 | 14 | jobs: 15 | include: 16 | 17 | - name: "Build Arduino ESP32" 18 | if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) 19 | stage: build 20 | script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 21 | 22 | - name: "Build Arduino ESP8266" 23 | if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) 24 | stage: build 25 | script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 26 | 27 | - name: "Build Platformio ESP32" 28 | if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) 29 | stage: build 30 | script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 1 1 31 | 32 | - name: "Build Platformio ESP8266" 33 | if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) 34 | stage: build 35 | script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 1 1 36 | 37 | notifications: 38 | email: 39 | on_success: change 40 | on_failure: change 41 | webhooks: 42 | urls: 43 | - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 44 | on_success: change # options: [always|never|change] default: always 45 | on_failure: always # options: [always|never|change] default: always 46 | on_start: never # options: [always|never|change] default: always 47 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "src" 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS 6 | "src" 7 | ) 8 | 9 | set(COMPONENT_REQUIRES 10 | "arduino-esp32" 11 | "AsyncTCP" 12 | ) 13 | 14 | register_component() 15 | 16 | target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32) 17 | target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti) 18 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS := src 2 | COMPONENT_SRCDIRS := src 3 | CXXFLAGS += -fno-rtti 4 | -------------------------------------------------------------------------------- /examples/CaptivePortal/CaptivePortal.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef ESP32 3 | #include 4 | #include 5 | #elif defined(ESP8266) 6 | #include 7 | #include 8 | #endif 9 | #include "ESPAsyncWebServer.h" 10 | 11 | DNSServer dnsServer; 12 | AsyncWebServer server(80); 13 | 14 | class CaptiveRequestHandler : public AsyncWebHandler { 15 | public: 16 | CaptiveRequestHandler() {} 17 | virtual ~CaptiveRequestHandler() {} 18 | 19 | bool canHandle(AsyncWebServerRequest *request){ 20 | //request->addInterestingHeader("ANY"); 21 | return true; 22 | } 23 | 24 | void handleRequest(AsyncWebServerRequest *request) { 25 | AsyncResponseStream *response = request->beginResponseStream("text/html"); 26 | response->print("Captive Portal"); 27 | response->print("

This is out captive portal front page.

"); 28 | response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); 29 | response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); 30 | response->print(""); 31 | request->send(response); 32 | } 33 | }; 34 | 35 | 36 | void setup(){ 37 | //your other setup stuff... 38 | WiFi.softAP("esp-captive"); 39 | dnsServer.start(53, "*", WiFi.softAPIP()); 40 | server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP 41 | //more handlers... 42 | server.begin(); 43 | } 44 | 45 | void loop(){ 46 | dnsServer.processNextRequest(); 47 | } 48 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/.exclude.files: -------------------------------------------------------------------------------- 1 | /*.gz 2 | /edit_gz 3 | /.exclude.files 4 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/ace.ico.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/ace.ico.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/acefull.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/acefull.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/edit_gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/edit_gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/favicon.ico -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/folder/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/folder/image.jpg -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/folder/test.txt: -------------------------------------------------------------------------------- 1 | Test -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/index.htm: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | WebSocketTester 23 | 52 | 124 | 125 | 126 |

127 |     
128 | $ 129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/worker-css.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/worker-css.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/worker-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/worker-html.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/worker-javascript.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/worker-javascript.js.gz -------------------------------------------------------------------------------- /examples/ESP_AsyncFSBrowser/data/worker-json.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/ESP_AsyncFSBrowser/data/worker-json.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/1.PNG -------------------------------------------------------------------------------- /examples/SmartSwitch/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/2.PNG -------------------------------------------------------------------------------- /examples/SmartSwitch/3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/3.PNG -------------------------------------------------------------------------------- /examples/SmartSwitch/4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/4.PNG -------------------------------------------------------------------------------- /examples/SmartSwitch/PinOut_Notes.txt: -------------------------------------------------------------------------------- 1 | This application: 2 | D2 = 4; // DHT DATA I/O 3 | D3 = 0; // BUTTON - most modules have it populated on PCB 4 | D4 = 2; // LED (RELAY) - most modules have it populated, on ESP32 is with reversed logic levels 5 | 6 | 7 | 8 | Pinout ESP12 (8266) 9 | D GPIO In Out Notes 10 | 11 | D0 16 no interrupt no PWM or I2C support HIGH at boot used to wake up from deep sleep 12 | D1 5 OK OK often used as SCL (I2C) 13 | D2 4 OK OK often used as SDA (I2C) 14 | D3 0 PU OK pulled up connected to FLASH button, boot fails if pulled LOW 15 | D4 2 PU OK pulled up HIGH at boot connected to on-board LED, boot fails if pulled LOW 16 | D5 14 OK OK SPI (SCLK) 17 | D6 12 OK OK SPI (MISO) 18 | D7 13 OK OK SPI (MOSI) 19 | D8 15 pulled to GND OK SPI (CS) Boot fails if pulled HIGH 20 | RX 3 OK RX pin HIGH at boot 21 | TX 1 TX pin OK HIGH at boot debug output at boot, boot fails if pulled LOW 22 | A0 ADC0 Analog Input 23 | 24 | 25 | Pinout ESP32 26 | IO In Out Notes 27 | 28 | 0 PU OK pulled-up input, outputs PWM signal at boot 29 | 1 TX OK debug output at boot 30 | 2 OK OK connected to on-board LED 31 | 3 OK RX HIGH at boot 32 | 4 OK OK 33 | 5 OK OK outputs PWM signal at boot 34 | 35 | 6-11 x x connected to the integrated SPI flash 36 | 37 | 12 OK OK boot fail if pulled high 38 | 13 OK OK 39 | 14 OK OK outputs PWM signal at boot 40 | 15 OK OK outputs PWM signal at boot 41 | 16 OK OK 42 | 17 OK OK 43 | 18 OK OK 44 | 19 OK OK 45 | 21 OK OK 46 | 22 OK OK 47 | 23 OK OK 48 | 25 OK OK 49 | 26 OK OK 50 | 27 OK OK 51 | 32 OK OK 52 | 33 OK OK 53 | 34 OK input only 54 | 35 OK input only 55 | 36 OK input only 56 | 39 OK input only -------------------------------------------------------------------------------- /examples/SmartSwitch/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## SmartSwitch 4 | - Remote Temperature Control application with schedule 5 | (example: car block heater or car battery charger for winter) 6 | - Based on [ESP_AsyncFSBrowser](https://github.com/lorol/ESPAsyncWebServer/tree/master/examples/ESP_AsyncFSBrowser) example that uses embedded ACE editor 7 | - Wide browser compatibility, no extra server-side needed 8 | - HTTP server and WebSocket on same port 9 | - Standalone, no JS dependencies for the browser from Internet 10 | - [Ace Editor](https://github.com/ajaxorg/ace) embedded to source but also - editable, upgradeable see [extras folder](https://github.com/lorol/ESPAsyncWebServer/tree/master/extras) 11 | - Added [ESPAsyncWiFiManager](https://github.com/alanswx/ESPAsyncWiFiManager) and fallback AP mode after timeout 12 | - Real Time (NTP) w/ Time Zones. Sync from browser time if in AP mode 13 | - Memorized settings to EEPROM 14 | - Multiple clients can be connected at same time, they see each other' requests 15 | - Authentication variants including [Cookie-based](https://github.com/me-no-dev/ESPAsyncWebServer/pull/684) idea 16 | - Used [this Xtea implementation](https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea) for getting a fancier Cookie token 17 | - Default credentials **smart : switch** or only **switch** as password 18 | - OTA included 19 | - Use the latest ESP8266 ESP32 cores from GitHub -------------------------------------------------------------------------------- /examples/SmartSwitch/Xtea.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Xtea.cpp - Xtea encryption/decryption 3 | Written by Frank Kienast in November, 2010 4 | https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea 5 | */ 6 | #include 7 | #include "Xtea.h" 8 | 9 | #define NUM_ROUNDS 32 10 | 11 | Xtea::Xtea(unsigned long key[4]) 12 | { 13 | _key[0] = key[0]; 14 | _key[1] = key[1]; 15 | _key[2] = key[2]; 16 | _key[3] = key[3]; 17 | } 18 | 19 | void Xtea::encrypt(unsigned long v[2]) 20 | { 21 | unsigned int i; 22 | unsigned long v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9; 23 | 24 | for (i=0; i < NUM_ROUNDS; i++) 25 | { 26 | v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _key[sum & 3]); 27 | sum += delta; 28 | v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _key[(sum>>11) & 3]); 29 | } 30 | 31 | v[0]=v0; v[1]=v1; 32 | } 33 | 34 | void Xtea::decrypt(unsigned long v[2]) 35 | { 36 | unsigned int i; 37 | uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*NUM_ROUNDS; 38 | 39 | for (i=0; i < NUM_ROUNDS; i++) 40 | { 41 | v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _key[(sum>>11) & 3]); 42 | sum -= delta; 43 | v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _key[sum & 3]); 44 | } 45 | 46 | v[0]=v0; v[1]=v1; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/SmartSwitch/Xtea.h: -------------------------------------------------------------------------------- 1 | /* 2 | Xtea.h - Crypto library 3 | Written by Frank Kienast in November, 2010 4 | https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea 5 | */ 6 | #ifndef Xtea_h 7 | #define Xtea_h 8 | 9 | 10 | class Xtea 11 | { 12 | public: 13 | Xtea(unsigned long key[4]); 14 | void encrypt(unsigned long data[2]); 15 | void decrypt(unsigned long data[2]); 16 | private: 17 | unsigned long _key[4]; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data/.exclude.files: -------------------------------------------------------------------------------- 1 | /*.gz 2 | /edit_gz 3 | /.exclude.files 4 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data/ace.ico.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/ace.ico.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/acefull.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/acefull.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/app.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/app.css.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/app.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/app.min.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/edit_gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/edit_gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/favicon.ico.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/favicon.ico.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/login/favicon.ico.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/login/favicon.ico.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/login/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 |

Login - Logout

15 |
16 |

17 |

18 | 19 |
20 |
Back 21 |
22 | 23 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data/worker-css.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/worker-css.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/worker-html.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/worker-html.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/worker-javascript.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/worker-javascript.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data/worker-json.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data/worker-json.js.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/.exclude.files: -------------------------------------------------------------------------------- 1 | *.gz 2 | .exclude.files 3 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/app.css: -------------------------------------------------------------------------------- 1 | .fpicker{background:#f2f2f2;padding:10px;width:100%;max-width:150px;white-space:nowrap;font-size:32px;font-weight:700;text-align:center}.tpop .fpicker{margin:20px auto 0 auto}.fpicker-h,.fpicker-m{display:inline-block;width:50%}.fpicker input[type=text]{box-sizing:border-box;width:70%;padding:2px;margin:5px 0;border:0;background:#fff;color:#888;text-align:center;font-size:24px}.fpicker-down,.fpicker-up{text-align:center;color:#468c00;cursor:pointer}.fpicker-btn{margin-top:10px}.fpicker-btn input[type=button]{width:50%;padding:10px 0;border:0;background:#468c00;color:#fff;font-size:20px;cursor:pointer}.ncf-container{font-size:14px;box-sizing:border-box;position:fixed;z-index:999999}.ncf-container.nfc-top-left{top:12px;left:12px}.ncf-container.nfc-top-right{top:12px;right:12px}.ncf-container.nfc-bottom-right{bottom:12px;right:12px}.ncf-container.nfc-bottom-left{bottom:12px;left:12px}@media (max-width:350px){.ncf-container{left:0;right:0}}.ncf-container .ncf{background:#fff;transition:.3s ease;position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:30px;width:100px;border-radius:3px 3px 3px 3px;box-shadow:0 0 12px #999;color:#000;opacity:.9;background-position:15px!important;background-repeat:no-repeat!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ncf-container .ncf:hover{box-shadow:0 0 12px #000;opacity:1;cursor:pointer}.ncf-container .ncf .ncf-title{font-weight:700;font-size:16px;text-align:left;margin-top:0;margin-bottom:6px;word-wrap:break-word}.ncf-container .ncf .nfc-message{margin:0;text-align:left;word-wrap:break-word}.ncf-container .success{background:#51a351;color:#fff;padding:15px 15px 15px 50px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)}.ncf-container .info{background:#2f96b4;color:#fff;padding:15px 15px 15px 50px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)}.ncf-container .warning{background:#f87400;color:#fff;padding:15px 15px 15px 50px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)}.ncf-container .error{background:#bd362f;color:#fff;padding:15px 15px 15px 50px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}.ncf-container button{position:relative;right:-.3em;top:-.3em;float:right;font-weight:700;color:#fff;text-shadow:0 1px 0 #fff;opacity:.8;line-height:1;font-size:16px;padding:0;cursor:pointer;background:0 0;border:0}.ncf-container button:hover{opacity:1}.tpop{position:fixed;top:0;left:0;width:100%;height:100vh;background:rgba(0,0,0,.7);transition:all .5s;visibility:hidden;opacity:0}.tpop.show{visibility:visible;opacity:1}.tpicker{background:#f2f2f2;padding:10px;width:100%;max-width:150px;white-space:nowrap;font-size:32px;font-weight:700;text-align:center}.tpop .tpicker{margin:20px auto 0 auto}.tpicker-h,.tpicker-m{display:inline-block;width:50%}.tpicker input[type=text]{box-sizing:border-box;width:70%;padding:10px;margin:5px 0;border:0;background:#fff;color:#888;text-align:center;font-size:24px}.tpicker-down,.tpicker-up{text-align:center;color:#ff853f;cursor:pointer}.tpicker-btn{margin-top:10px}.tpicker-btn input[type=button]{width:50%;padding:10px 0;border:0;background:#a83a0b;color:#fff;font-size:20px;cursor:pointer} -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data_src/favicon.ico -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/.exclude.files: -------------------------------------------------------------------------------- 1 | /*.js.gz 2 | /.exclude.files 3 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/1-inline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Inline Timepicker Example 5 | 6 | 7 | 8 | 13 | 14 | 15 |

Inline Time Picker

16 | 17 |
TIME ON
18 |
19 |
TIME OFF
20 | 21 | 22 | 29 | 30 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/2-popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Popup Timeicker Example 5 | 6 | 7 | 8 | 11 | 12 | 13 |

Popup Time Picker

14 | 15 | 16 | ON/OFF 17 |

Line

18 | 19 | 20 | 26 | 27 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/README.txt: -------------------------------------------------------------------------------- 1 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 2 | LICENSE 3 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 4 | 5 | Copyright 2018 by Code Boxx 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | 26 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 27 | MORE 28 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 29 | Please visit https://code-boxx.com/ for more! -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/tpick-light-pop.css: -------------------------------------------------------------------------------- 1 | /* [CONTAINER] */ 2 | .tpop { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100vh; 8 | background: rgba(0, 0, 0, 0.7); 9 | transition: all 0.5s; 10 | visibility: hidden; 11 | opacity: 0; 12 | } 13 | .tpop.show { 14 | visibility: visible; 15 | opacity: 1; 16 | } 17 | .tpicker { 18 | background: #f2f2f2; 19 | padding: 10px; 20 | width: 100%; 21 | max-width: 320px; 22 | white-space: nowrap; 23 | font-size: 32px; 24 | font-weight: bold; 25 | text-align: center; 26 | } 27 | .tpop .tpicker { 28 | margin: 20px auto 0 auto; 29 | } 30 | 31 | /* [HR + MIN + AM/PM] */ 32 | .tpicker-h, .tpicker-m, .tpicker-ap { 33 | display: inline-block; 34 | width: 30%; 35 | } 36 | .tpicker input[type=text] { 37 | box-sizing: border-box; 38 | width: 70%; 39 | padding: 10px; 40 | margin: 5px 0; 41 | border: 0; 42 | background: #fff; 43 | color: #888; 44 | text-align: center; 45 | font-size: 28px; 46 | } 47 | .tpicker-up, .tpicker-down { 48 | text-align: center; 49 | color: #ff853f; 50 | cursor: pointer; 51 | } 52 | 53 | /* [CANCEL + OK BUTTON] */ 54 | .tpicker-btn { 55 | margin-top: 10px; 56 | } 57 | .tpicker-btn input[type=button] { 58 | width: 50%; 59 | padding: 10px 0; 60 | border: 0; 61 | background: #a83a0b; 62 | color: #fff; 63 | font-size: 20px; 64 | cursor: pointer; 65 | } -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/tpick-light.css: -------------------------------------------------------------------------------- 1 | /* [CONTAINER] */ 2 | .tpicker { 3 | background: #f2f2f2; 4 | padding: 10px; 5 | width: 100%; 6 | max-width: 320px; 7 | white-space: nowrap; 8 | font-size: 32px; 9 | font-weight: bold; 10 | text-align: center; 11 | } 12 | 13 | /* [HR + MIN + AM/PM] */ 14 | .tpicker-h, .tpicker-m, .tpicker-ap { 15 | display: inline-block; 16 | width: 30%; 17 | } 18 | .tpicker input[type=text] { 19 | box-sizing: border-box; 20 | width: 70%; 21 | padding: 10px; 22 | margin: 5px 0; 23 | border: 0; 24 | background: #fff; 25 | color: #888; 26 | text-align: center; 27 | font-size: 28px; 28 | } 29 | .tpicker-up, .tpicker-down { 30 | text-align: center; 31 | color: #ff853f; 32 | cursor: pointer; 33 | } 34 | 35 | /* [CANCEL + OK BUTTON] */ 36 | .tpicker-btn { 37 | margin-top: 10px; 38 | } 39 | .tpicker-btn input[type=button] { 40 | width: 50%; 41 | padding: 10px 0; 42 | border: 0; 43 | background: #a83a0b; 44 | color: #fff; 45 | font-size: 20px; 46 | cursor: pointer; 47 | } -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/tpick-pop.js: -------------------------------------------------------------------------------- 1 | var tpick = { 2 | attach : function (target) { 3 | // attach() : attach time picker to target 4 | 5 | // Generate a unique random ID for the time picker 6 | var uniqueID = 0; 7 | while (document.getElementById("tpick-" + uniqueID) != null) { 8 | uniqueID = Math.floor(Math.random() * (100 - 2)) + 1; 9 | } 10 | 11 | // Create wrapper 12 | var tw = document.createElement("div"); 13 | tw.id = "tpick-" + uniqueID; 14 | tw.classList.add("tpop"); 15 | tw.dataset.target = target; 16 | tw.addEventListener("click", function (evt) { 17 | if (evt.target.classList.contains("tpop")) { 18 | this.classList.remove("show"); 19 | } 20 | }); 21 | 22 | // Create new time picker 23 | var tp = document.createElement("div"); 24 | tp.classList.add("tpicker"); 25 | 26 | // Create hour picker 27 | tp.appendChild(this.draw("h")); 28 | tp.appendChild(this.draw("m")); 29 | tp.appendChild(this.draw("ap")); 30 | 31 | // OK button 32 | var bottom = document.createElement("div"), 33 | ok = document.createElement("input"); 34 | ok.setAttribute("type", "button"); 35 | ok.value = "OK"; 36 | ok.addEventListener("click", function(){ tpick.set(this); }); 37 | bottom.classList.add("tpicker-btn"); 38 | bottom.appendChild(ok); 39 | tp.appendChild(bottom); 40 | 41 | // Attach time picker to body 42 | tw.appendChild(tp); 43 | document.body.appendChild(tw); 44 | 45 | // Attach on focus event 46 | var target = document.getElementById(target); 47 | target.dataset.dp = uniqueID; 48 | target.onfocus = function () { 49 | document.getElementById("tpick-" + this.dataset.dp).classList.add("show"); 50 | }; 51 | }, 52 | 53 | draw : function (type) { 54 | // draw() : support function to create the hr, min, am/pm selector 55 | 56 | // Create the controls 57 | var docket = document.createElement("div"), 58 | up = document.createElement("div"), 59 | down = document.createElement("div"), 60 | text = document.createElement("input"); 61 | docket.classList.add("tpicker-" + type); 62 | up.classList.add("tpicker-up"); 63 | down.classList.add("tpicker-down"); 64 | up.innerHTML = "︿"; 65 | down.innerHTML = "﹀"; 66 | text.readOnly = true; 67 | text.setAttribute("type", "text"); 68 | 69 | // Default values + click event 70 | // You can do your own modifications here 71 | if (type=="h") { 72 | text.value = "12"; 73 | up.addEventListener("click", function(){ tpick.spin("h", 1, this); }); 74 | down.addEventListener("click", function(){ tpick.spin("h", 0, this); }); 75 | } else if (type=="m") { 76 | text.value = "10"; 77 | up.addEventListener("click", function(){ tpick.spin("m", 1, this); }); 78 | down.addEventListener("click", function(){ tpick.spin("m", 0, this); }); 79 | } else { 80 | text.value = "AM"; 81 | up.addEventListener("click", function(){ tpick.spin("ap", 1, this); }); 82 | down.addEventListener("click", function(){ tpick.spin("ap", 0, this); }); 83 | } 84 | 85 | // Complete + return the docket 86 | docket.appendChild(up); 87 | docket.appendChild(text); 88 | docket.appendChild(down); 89 | return docket; 90 | }, 91 | 92 | spin : function (type, direction, el) { 93 | // spin() : when the up/down button is pressed 94 | 95 | // Get current field + value 96 | var parent = el.parentElement, 97 | field = parent.getElementsByTagName("input")[0], 98 | value = field.value; 99 | 100 | // Spin it 101 | if (type=="h") { 102 | value = parseInt(value); 103 | if (direction) { value++; } else { value--; } 104 | if (value==0) { value = 12; } 105 | else if (value>12) { value = 1; } 106 | } else if (type=="m") { 107 | value = parseInt(value); 108 | if (direction) { value+=5; } else { value-=5; } 109 | if (value<0) { value = 55; } 110 | else if (value>60) { value = 0; } 111 | if (value<10) { value = "0" + value; } 112 | } 113 | else { 114 | value = value=="PM" ? "AM" : "PM"; 115 | } 116 | field.value = value; 117 | }, 118 | 119 | set : function (el) { 120 | // set() : set the selected time on the target 121 | 122 | // Get the parent container 123 | var parent = el.parentElement; 124 | while (parent.classList.contains("tpop") == false) { 125 | parent = parent.parentElement; 126 | } 127 | 128 | // Formulate + set selected time 129 | var input = parent.querySelectorAll("input[type=text]"); 130 | var time = input[0].value + ":" + input[1].value + " " + input[2].value; 131 | document.getElementById(parent.dataset.target).value = time; 132 | 133 | // Close popup 134 | parent.classList.remove("show"); 135 | } 136 | }; -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/js-time-picker/tpick.js: -------------------------------------------------------------------------------- 1 | var tpick = { 2 | attach : function (container, target) { 3 | // attach() : attach time picker to target 4 | 5 | // Generate a unique random ID for the time picker 6 | var uniqueID = 0; 7 | while (document.getElementById("tpick-" + uniqueID) != null) { 8 | uniqueID = Math.floor(Math.random() * (100 - 2)) + 1; 9 | } 10 | 11 | // Create new time picker 12 | var tp = document.createElement("div"); 13 | tp.id = "tpick-" + uniqueID; 14 | tp.dataset.target = target; 15 | tp.classList.add("tpicker"); 16 | 17 | // Create hour picker 18 | tp.appendChild(this.draw("h")); 19 | tp.appendChild(this.draw("m")); 20 | tp.appendChild(this.draw("ap")); 21 | 22 | // OK button 23 | var bottom = document.createElement("div"), 24 | ok = document.createElement("input"); 25 | ok.setAttribute("type", "button"); 26 | ok.value = "OK"; 27 | ok.addEventListener("click", function(){ tpick.set(this); }); 28 | bottom.classList.add("tpicker-btn"); 29 | bottom.appendChild(ok); 30 | tp.appendChild(bottom); 31 | 32 | // Attach time picker to target container 33 | document.getElementById(container).appendChild(tp); 34 | }, 35 | 36 | draw : function (type) { 37 | // draw() : support function to create the hr, min, am/pm selector 38 | 39 | // Create the controls 40 | var docket = document.createElement("div"), 41 | up = document.createElement("div"), 42 | down = document.createElement("div"), 43 | text = document.createElement("input"); 44 | docket.classList.add("tpicker-" + type); 45 | up.classList.add("tpicker-up"); 46 | down.classList.add("tpicker-down"); 47 | up.innerHTML = "︿"; 48 | down.innerHTML = "﹀"; 49 | text.readOnly = true; 50 | text.setAttribute("type", "text"); 51 | 52 | // Default values + click event 53 | // You can do your own modifications here 54 | if (type=="h") { 55 | text.value = "12"; 56 | up.addEventListener("click", function(){ tpick.spin("h", 1, this); }); 57 | down.addEventListener("click", function(){ tpick.spin("h", 0, this); }); 58 | } else if (type=="m") { 59 | text.value = "10"; 60 | up.addEventListener("click", function(){ tpick.spin("m", 1, this); }); 61 | down.addEventListener("click", function(){ tpick.spin("m", 0, this); }); 62 | } else { 63 | text.value = "AM"; 64 | up.addEventListener("click", function(){ tpick.spin("ap", 1, this); }); 65 | down.addEventListener("click", function(){ tpick.spin("ap", 0, this); }); 66 | } 67 | 68 | // Complete + return the docket 69 | docket.appendChild(up); 70 | docket.appendChild(text); 71 | docket.appendChild(down); 72 | return docket; 73 | }, 74 | 75 | spin : function (type, direction, el) { 76 | // spin() : when the up/down button is pressed 77 | 78 | // Get current field + value 79 | var parent = el.parentElement, 80 | field = parent.getElementsByTagName("input")[0], 81 | value = field.value; 82 | 83 | // Spin it 84 | if (type=="h") { 85 | value = parseInt(value); 86 | if (direction) { value++; } else { value--; } 87 | if (value==0) { value = 12; } 88 | else if (value>12) { value = 1; } 89 | } else if (type=="m") { 90 | value = parseInt(value); 91 | if (direction) { value+=5; } else { value-=5; } 92 | if (value<0) { value = 55; } 93 | else if (value>60) { value = 0; } 94 | if (value<10) { value = "0" + value; } 95 | } 96 | else { 97 | value = value=="PM" ? "AM" : "PM"; 98 | } 99 | field.value = value; 100 | }, 101 | 102 | set : function (el) { 103 | // set() : set the selected time on the target 104 | 105 | // Get the parent container 106 | var parent = el.parentElement; 107 | while (parent.classList.contains("tpicker") == false) { 108 | parent = parent.parentElement; 109 | } 110 | 111 | // Formulate + set selected time 112 | var input = parent.querySelectorAll("input[type=text]"); 113 | var time = input[0].value + ":" + input[1].value + " " + input[2].value; 114 | document.getElementById(parent.dataset.target).value = time; 115 | } 116 | }; -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/auto-adjust.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Auto-adjust 6 | 7 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 |

40 | JustGage auto-adjusts to the size of containing element. And to the screen zoom level. And screen density. Nice. This means you’ll get clean, sharp and nice looking gauge at all times. Try zooming the page to see the results. 41 |

42 | 43 | 44 | 45 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/counter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter 7 | 8 | 28 | 29 | 30 | 31 |
32 |
33 | Random Refresh 34 |
35 | 36 | 37 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/custom-interval.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Custom interval 6 | 7 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |

35 | You need to measure, say, between 350 and 980? No problem, just tell it to justGage. Displayed value and color are calculated as a percentage in defined range, with optional min and max labels shown. 36 |

37 |

38 | Also, if displayed value is out of range, relax and kick your feet up - justGage will take care of it for you. 39 |

40 | 41 | 42 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/custom-node.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Custom Node 6 | 7 | 8 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 |
52 | 53 | 54 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/custom-sectors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Sectors 7 | 8 | 9 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |

0-50 is green, 51-100 is red

39 | Random Refresh 40 | Update Sectors 41 |
42 | 43 | 44 | 45 | 87 | 88 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/custom-value-renderer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Render Function 7 | 79 | 80 | 81 | 82 |
83 | Random Refresh 84 | 85 | 86 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/customize-style.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Customize style 6 | 7 | 8 | 26 | 27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |

36 | Not digging default style? Then mock your own, Picasso! JustGage features bunch of styling options including gauge width, gauge color and shadow, gauge level colors, colors for title, value, min & max etc. 37 |

38 |

39 | Check non-minified version of justgage.js for list of all setup parameters. 40 |

41 | 42 | 43 | 44 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/defaults.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Defaults 7 | 8 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 | 30 | 31 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/font-options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter 7 | 8 | 28 | 29 | 30 | 31 |
32 |
33 | Random Refresh 34 |
35 | 36 | 37 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/format-number.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter 7 | 8 | 28 | 29 | 30 | 31 |
32 |
33 | Random Refresh 34 |
35 | 36 | 37 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/html5-data-attribute-config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | html5 data-attribute setup 7 | 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 53 | 54 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/human-friendly-numbers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Guage 7 | 79 | 80 | 81 | 82 |
83 | Random Refresh 84 | 85 | 86 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/pointer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pointer 7 | 8 | 49 | 50 | 51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 | 71 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/refresh-maximum.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Donuts, baby! 7 | 8 | 28 | 29 | 30 | 31 |
32 |
33 | Random Refresh 34 |
35 | Set Max 100 36 | Set Max 200 37 | Set Max 400 38 |
39 | 40 | 41 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/responsive-gauges.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dynamic Resize 6 | 7 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/justgage-1.2.2/examples/reverse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Reverse 7 | 8 | 49 | 50 | 51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 | 71 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | "tab" 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "windows" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ], 28 | "no-console": 0, 29 | "no-undef": 0 30 | } 31 | }; -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/.gitignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .idea/ 3 | .DS_Store 4 | 5 | # Build directories 6 | build/ 7 | 8 | # Dependency directories 9 | node_modules/ 10 | jspm_packages/ 11 | 12 | # Lock files 13 | yarn.lock 14 | package-lock.json 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional eslint cache 27 | .eslintcache 28 | 29 | # Yarn Integrity file 30 | .yarn-integrity 31 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/LICENSE.md: -------------------------------------------------------------------------------- 1 | # Notifications license 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/__tests__/helpers.test.js: -------------------------------------------------------------------------------- 1 | const { partial, append, isString, createElement, createParagraph } = require('../src/helpers'); 2 | 3 | const addNumbers = (x, y) => x + y; 4 | 5 | const sum = (...numbers) => numbers.reduce((total, current) => total + current, 0); 6 | 7 | describe('Helpers', () => { 8 | beforeEach(() => { 9 | document.body.innerHTML = ''; 10 | }); 11 | 12 | describe('Partial', () => { 13 | it('should return a partially applied function', () => { 14 | expect(typeof partial(addNumbers, 10)).toEqual('function'); 15 | }); 16 | 17 | it('should execute function when partially applied function is called', () => { 18 | expect(partial(addNumbers, 20)(10)).toEqual(30); 19 | }); 20 | 21 | it('should gather argument', () => { 22 | expect(partial(sum, 5, 10)(15, 20, 25)).toEqual(75); 23 | }); 24 | }); 25 | 26 | describe('Append', () => { 27 | const container = document.createElement('div'); 28 | document.body.appendChild(container); 29 | 30 | const elementToAppend = document.createElement('h1'); 31 | elementToAppend.classList.add('heading'); 32 | elementToAppend.innerText = 'working'; 33 | 34 | append(container, elementToAppend); 35 | 36 | const element = document.querySelector('.heading'); 37 | expect(element); 38 | 39 | expect(element.innerText).toEqual('working'); 40 | }); 41 | 42 | describe('Is string', () => { 43 | expect(isString(1)).toEqual(false); 44 | expect(isString(null)).toEqual(false); 45 | expect(isString(undefined)).toEqual(false); 46 | expect(isString({})).toEqual(false); 47 | 48 | expect(isString('')).toEqual(true); 49 | expect(isString('a')).toEqual(true); 50 | expect(isString('1')).toEqual(true); 51 | expect(isString('some string')).toEqual(true); 52 | }); 53 | 54 | describe('Create element', () => { 55 | it('should create an element', () => { 56 | expect(createElement('p')).toEqual(document.createElement('p')); 57 | expect(createElement('h1')).toEqual(document.createElement('h1')); 58 | expect(createElement('ul')).toEqual(document.createElement('ul')); 59 | expect(createElement('li')).toEqual(document.createElement('li')); 60 | expect(createElement('div')).toEqual(document.createElement('div')); 61 | expect(createElement('span')).toEqual(document.createElement('span')); 62 | }); 63 | 64 | it('should add class names', () => { 65 | expect(createElement('div', 'someclass1', 'someclass2').classList.contains('someclass2')); 66 | expect(createElement('p', 'para', 'test').classList.contains('para')); 67 | 68 | const mockUl = document.createElement('ul'); 69 | mockUl.classList.add('nav'); 70 | mockUl.classList.add('foo'); 71 | 72 | expect(createElement('ul', 'nav', 'foo').classList).toEqual(mockUl.classList); 73 | }); 74 | }); 75 | 76 | describe('Create paragraph', () => { 77 | it('should create a paragraph', () => { 78 | const p = document.createElement('p'); 79 | p.innerText = 'Some text'; 80 | expect(createParagraph()('Some text')).toEqual(p); 81 | }); 82 | 83 | it('should add class names', () => { 84 | const p = document.createElement('p'); 85 | p.classList.add('body-text'); 86 | p.classList.add('para'); 87 | 88 | expect(createParagraph('body-text', 'para')('')).toEqual(p); 89 | }); 90 | 91 | it('should set inner text', () => { 92 | const p = document.createElement('p'); 93 | p.innerText = 'Hello world!'; 94 | p.classList.add('text'); 95 | 96 | expect(createParagraph('text')('Hello world!')).toEqual(p); 97 | }); 98 | 99 | it('should append to DOM', () => { 100 | append(document.body, createParagraph('text')('hello')); 101 | expect(document.querySelector('.text').innerText).toEqual('hello'); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/__tests__/index.tests.js: -------------------------------------------------------------------------------- 1 | require('../src/index'); 2 | 3 | describe('Notifications', () => { 4 | beforeEach(() => { 5 | document.body.innerHTML = ''; 6 | }); 7 | 8 | it('should display a console warning if no title or message is passed', () => { 9 | jest.spyOn(global.console, 'warn'); 10 | window.createNotification()(); 11 | expect(console.warn).toBeCalled(); 12 | }); 13 | 14 | it('should render a default notification', () => { 15 | const notification = window.createNotification(); 16 | 17 | const title = 'I am a title'; 18 | 19 | // Should initially not contain any notifications 20 | expect(document.querySelectorAll('.ncf').length).toEqual(0); 21 | 22 | // Create a notification instance with a title 23 | notification({ title }); 24 | 25 | // Should be one notification with the title passed in 26 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 27 | expect(document.querySelector('.ncf-title').innerText).toEqual(title); 28 | 29 | // Create a second instance so there should now be two instances 30 | notification({ title }); 31 | expect(document.querySelectorAll('.ncf').length).toEqual(2); 32 | }); 33 | 34 | it('should close on click if the option is enabled', () => { 35 | const notification = window.createNotification({ 36 | closeOnClick: true 37 | }); 38 | 39 | // Create a notification with a generic body 40 | notification({ message: 'some text' }); 41 | 42 | // Should be one notification instance 43 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 44 | 45 | // Click the notification 46 | document.querySelector('.ncf').click(); 47 | 48 | expect(document.querySelectorAll('.ncf').length).toEqual(0); 49 | }); 50 | 51 | it('should not close on click if the option is disabled', () => { 52 | const notification = window.createNotification({ 53 | closeOnClick: false 54 | }); 55 | 56 | // Create a notification with a generic body 57 | notification({ message: 'some text' }); 58 | 59 | // Should be one notification instance 60 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 61 | 62 | // Click the notification 63 | document.querySelector('.ncf').click(); 64 | 65 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 66 | }); 67 | 68 | it('should set position class if valid', () => { 69 | const validPositions = [ 70 | 'nfc-top-left', 71 | 'nfc-top-right', 72 | 'nfc-bottom-left', 73 | 'nfc-bottom-right' 74 | ]; 75 | 76 | validPositions.forEach(position => { 77 | const notification = window.createNotification({ 78 | positionClass: position 79 | }); 80 | 81 | notification({ title: 'title here' }); 82 | 83 | const className = `.${position}`; 84 | 85 | expect(document.querySelectorAll(className).length).toEqual(1); 86 | 87 | const container = document.querySelector(className); 88 | expect(container.querySelectorAll('.ncf').length).toEqual(1); 89 | }); 90 | }); 91 | 92 | it('should revert to default to default position and warn if class is invalid', () => { 93 | const notification = window.createNotification({ 94 | positionClass: 'invalid-name' 95 | }); 96 | 97 | jest.spyOn(global.console, 'warn'); 98 | 99 | notification({ message: 'test' }); 100 | 101 | expect(console.warn).toBeCalled(); 102 | 103 | expect(document.querySelectorAll('.nfc-top-right').length).toEqual(1); 104 | }); 105 | 106 | it('should allow a custom onclick callback', () => { 107 | let a = 'not clicked'; 108 | 109 | const notification = window.createNotification({ 110 | onclick: () => { 111 | a = 'clicked'; 112 | } 113 | }); 114 | 115 | notification({ message: 'click test' }); 116 | 117 | expect(a).toEqual('not clicked'); 118 | 119 | // Click the notification 120 | document.querySelector('.ncf').click(); 121 | 122 | expect(a).toEqual('clicked'); 123 | }); 124 | 125 | it('should show for correct duration', () => { 126 | const notification = window.createNotification({ 127 | showDuration: 500 128 | }); 129 | 130 | notification({ message: 'test' }); 131 | 132 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 133 | 134 | // Should exist after 400ms 135 | setTimeout(() => { 136 | expect(document.querySelectorAll('.ncf').length).toEqual(1); 137 | }, 400); 138 | 139 | // Should delete after 500ms 140 | setTimeout(() => { 141 | expect(document.querySelectorAll('.ncf').length).toEqual(0); 142 | }); 143 | }, 501); 144 | }); 145 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/demo/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Written using ES5 JS for browser support 4 | window.addEventListener('DOMContentLoaded', function () { 5 | var form = document.querySelector('form'); 6 | 7 | form.addEventListener('submit', function (e) { 8 | e.preventDefault(); 9 | 10 | // Form elements 11 | var title = form.querySelector('#title').value; 12 | var message = form.querySelector('#message').value; 13 | var position = form.querySelector('#position').value; 14 | var duration = form.querySelector('#duration').value; 15 | var theme = form.querySelector('#theme').value; 16 | var closeOnClick = form.querySelector('#close').checked; 17 | var displayClose = form.querySelector('#closeButton').checked; 18 | 19 | if(!message) { 20 | message = 'You did not enter a message...'; 21 | } 22 | 23 | window.createNotification({ 24 | closeOnClick: closeOnClick, 25 | displayCloseButton: displayClose, 26 | positionClass: position, 27 | showDuration: duration, 28 | theme: theme 29 | })({ 30 | title: title, 31 | message: message 32 | }); 33 | }); 34 | }); -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notifications 6 | 7 | 8 | 9 | 49 | 50 | 51 | 52 | 53 | 54 |

Notifications

55 |
56 | 57 |
58 | 59 | 60 | 61 |
62 | 63 | 64 | 65 |
66 | 72 | 73 | 74 |
75 | 76 | 77 | 78 |
79 | 86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/demo/index_my.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notifications 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/dist/notifications.css: -------------------------------------------------------------------------------- 1 | .ncf-container{font-size:14px;box-sizing:border-box;position:fixed;z-index:999999}.ncf-container.nfc-top-left{top:12px;left:12px}.ncf-container.nfc-top-right{top:12px;right:12px}.ncf-container.nfc-bottom-right{bottom:12px;right:12px}.ncf-container.nfc-bottom-left{bottom:12px;left:12px}@media (max-width:767px){.ncf-container{left:0;right:0}}.ncf-container .ncf{background:#fff;transition:.3s ease;position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:30px;width:300px;border-radius:3px 3px 3px 3px;box-shadow:0 0 12px #999;color:#000;opacity:.9;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=90);filter:alpha(opacity=90);background-position:15px!important;background-repeat:no-repeat!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ncf-container .ncf:hover{box-shadow:0 0 12px #000;opacity:1;cursor:pointer}.ncf-container .ncf .ncf-title{font-weight:700;font-size:16px;text-align:left;margin-top:0;margin-bottom:6px;word-wrap:break-word}.ncf-container .ncf .nfc-message{margin:0;text-align:left;word-wrap:break-word}.ncf-container .success{background:#51a351;color:#fff;padding:15px 15px 15px 50px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==")}.ncf-container .info{background:#2f96b4;color:#fff;padding:15px 15px 15px 50px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=")}.ncf-container .warning{background:#f87400;color:#fff;padding:15px 15px 15px 50px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=")}.ncf-container .error{background:#bd362f;color:#fff;padding:15px 15px 15px 50px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=")!important}.ncf-container button{position:relative;right:-.3em;top:-.3em;float:right;font-weight:700;color:#fff;text-shadow:0 1px 0 #fff;opacity:.8;line-height:1;font-size:16px;padding:0;cursor:pointer;background:transparent;border:0}.ncf-container button:hover{opacity:1} -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/dist/notifications.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/examples/SmartSwitch/data_src/js_css_src/styled-notifications/dist/notifications.css.gz -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/dist/notifications.js: -------------------------------------------------------------------------------- 1 | !function(t){function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var e={};n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=0)}([function(t,n,e){e(1),t.exports=e(4)},function(t,n,e){"use strict";var i=Object.assign||function(t){for(var n=1;n-1}(t.positionClass)||(console.warn("An invalid notification position class has been specified."),t.positionClass=c.positionClass),t.onclick&&"function"!=typeof t.onclick&&(console.warn("Notification on click must be a function."),t.onclick=c.onclick),"number"!=typeof t.showDuration&&(t.showDuration=c.showDuration),(0,o.isString)(t.theme)&&0!==t.theme.length||(console.warn("Notification theme must be a string with length"),t.theme=c.theme),t}function e(t){return t=n(t),function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=n.title,i=n.message,c=r(t.positionClass);if(!e&&!i)return console.warn("Notification must contain a title or a message!");var a=(0,o.createElement)("div","ncf",t.theme);if(!0===t.closeOnClick&&a.addEventListener("click",function(){return c.removeChild(a)}),t.onclick&&a.addEventListener("click",function(n){return t.onclick(n)}),t.displayCloseButton){var s=(0,o.createElement)("button");s.innerText="X",!1===t.closeOnClick&&s.addEventListener("click",function(){return c.removeChild(a)}),(0,o.append)(a,s)}if((0,o.isString)(e)&&e.length&&(0,o.append)(a,(0,o.createParagraph)("ncf-title")(e)),(0,o.isString)(i)&&i.length&&(0,o.append)(a,(0,o.createParagraph)("nfc-message")(i)),(0,o.append)(c,a),t.showDuration&&t.showDuration>0){var l=setTimeout(function(){c.removeChild(a),0===c.querySelectorAll(".ncf").length&&document.body.removeChild(c)},t.showDuration);(t.closeOnClick||t.displayCloseButton)&&a.addEventListener("click",function(){return clearTimeout(l)})}}}function r(t){var n=document.querySelector("."+t);return n||(n=(0,o.createElement)("div","ncf-container",t),(0,o.append)(document.body,n)),n}var c={closeOnClick:!0,displayCloseButton:!1,positionClass:"nfc-top-right",onclick:!1,showDuration:3500,theme:"success"};t.createNotification?console.warn("Window already contains a create notification function. Have you included the script twice?"):t.createNotification=e}(window)},function(t,n,e){"use strict";!function(){function t(t){this.el=t;for(var n=t.className.replace(/^\s+|\s+$/g,"").split(/\s+/),i=0;i1?n-1:0),i=1;i1?n-1:0),i=1;i1?n-1:0),i=1;i1?n-1:0),c=1;c` 23 | 24 | 2. Link to notifications.js `` 25 | 26 | ## Usage 27 | ### Custom options 28 | - closeOnClick - Close the notification dialog when a click is invoked. 29 | - displayCloseButton - Display a close button in the top right hand corner of the notification. 30 | - positionClass - Set the position of the notification dialog. Accepted positions: ('nfc-top-right', 'nfc-bottom-right', 'nfc-bottom-left', 'nfc-top-left'). 31 | - onClick - Call a callback function when a click is invoked on a notification. 32 | - showDuration - Milliseconds the notification should be visible (0 for a notification that will remain open until clicked) 33 | - theme - Set the position of the notification dialog. Accepted positions: ('success', 'info', 'warning', 'error', 'A custom clasName'). 34 | ``` 35 | const defaultOptions = { 36 | closeOnClick: true, 37 | displayCloseButton: false, 38 | positionClass: 'nfc-top-right', 39 | onclick: false, 40 | showDuration: 3500, 41 | theme: 'success' 42 | }; 43 | ``` 44 | 45 | ## Example 46 | 47 | ### Success notification 48 | ``` 49 | // Create a success notification instance 50 | const successNotification = window.createNotification({ 51 | theme: 'success', 52 | showDuration: 5000 53 | }); 54 | 55 | // Invoke success notification 56 | successNotification({ 57 | message: 'Simple success notification' 58 | }); 59 | 60 | // Use the same instance but pass a title 61 | successNotification({ 62 | title: 'Working', 63 | message: 'Simple success notification' 64 | }); 65 | ``` 66 | 67 | ### Information notification 68 | ``` 69 | // Only running it once? Invoke immediately like this 70 | window.createNotification({ 71 | theme: 'success', 72 | showDuration: 5000 73 | })({ 74 | message: 'I have some information for you...' 75 | }); 76 | ``` 77 | 78 | ### Todo 79 | ~~1. Add to NPM~~ 80 | 2. Improve documentation 81 | 3. Further device testing 82 | 4. Add contributor instructions -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/src/helpers.js: -------------------------------------------------------------------------------- 1 | export const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs); 2 | 3 | export const append = (el, ...children) => children.forEach(child => el.appendChild(child)); 4 | 5 | export const isString = input => typeof input === 'string'; 6 | 7 | export const createElement = (elementType, ...classNames) => { 8 | const element = document.createElement(elementType); 9 | 10 | if(classNames.length) { 11 | classNames.forEach(currentClass => element.classList.add(currentClass)); 12 | } 13 | 14 | return element; 15 | }; 16 | 17 | const setInnerText = (element, text) => { 18 | element.innerText = text; 19 | return element; 20 | }; 21 | 22 | const createTextElement = (elementType, ...classNames) => partial(setInnerText, createElement(elementType, ...classNames)); 23 | 24 | export const createParagraph = (...classNames) => createTextElement('p', ...classNames); -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Polyfills 4 | import './polyfills/classList'; 5 | 6 | import { 7 | append, 8 | createElement, 9 | createParagraph, 10 | isString 11 | } from './helpers'; 12 | 13 | (function Notifications(window) { 14 | // Default notification options 15 | const defaultOptions = { 16 | closeOnClick: true, 17 | displayCloseButton: false, 18 | positionClass: 'nfc-top-right', 19 | onclick: false, 20 | showDuration: 3500, 21 | theme: 'success' 22 | }; 23 | 24 | function configureOptions(options) { 25 | // Create a copy of options and merge with defaults 26 | options = Object.assign({}, defaultOptions, options); 27 | 28 | // Validate position class 29 | function validatePositionClass(className) { 30 | const validPositions = [ 31 | 'nfc-top-left', 32 | 'nfc-top-right', 33 | 'nfc-bottom-left', 34 | 'nfc-bottom-right' 35 | ]; 36 | 37 | return validPositions.indexOf(className) > -1; 38 | } 39 | 40 | // Verify position, if invalid reset to default 41 | if (!validatePositionClass(options.positionClass)) { 42 | console.warn('An invalid notification position class has been specified.'); 43 | options.positionClass = defaultOptions.positionClass; 44 | } 45 | 46 | // Verify onClick is a function 47 | if (options.onclick && typeof options.onclick !== 'function') { 48 | console.warn('Notification on click must be a function.'); 49 | options.onclick = defaultOptions.onclick; 50 | } 51 | 52 | // Verify show duration 53 | if(typeof options.showDuration !== 'number') { 54 | options.showDuration = defaultOptions.showDuration; 55 | } 56 | 57 | // Verify theme 58 | if(!isString(options.theme) || options.theme.length === 0) { 59 | console.warn('Notification theme must be a string with length'); 60 | options.theme = defaultOptions.theme; 61 | } 62 | 63 | return options; 64 | } 65 | 66 | // Create a new notification instance 67 | function createNotification(options) { 68 | // Validate options and set defaults 69 | options = configureOptions(options); 70 | 71 | // Return a notification function 72 | return function notification({ title, message } = {}) { 73 | const container = createNotificationContainer(options.positionClass); 74 | 75 | if(!title && !message) { 76 | return console.warn('Notification must contain a title or a message!'); 77 | } 78 | 79 | // Create the notification wrapper 80 | const notificationEl = createElement('div', 'ncf', options.theme); 81 | 82 | // Close on click 83 | if(options.closeOnClick === true) { 84 | notificationEl.addEventListener('click', () => container.removeChild(notificationEl)); 85 | } 86 | 87 | // Custom click callback 88 | if(options.onclick) { 89 | notificationEl.addEventListener('click', (e) => options.onclick(e)); 90 | } 91 | 92 | // Display close button 93 | if(options.displayCloseButton) { 94 | const closeButton = createElement('button'); 95 | closeButton.innerText = 'X'; 96 | 97 | // Use the wrappers close on click to avoid useless event listeners 98 | if(options.closeOnClick === false){ 99 | closeButton.addEventListener('click', () =>container.removeChild(notificationEl)); 100 | } 101 | 102 | append(notificationEl, closeButton); 103 | } 104 | 105 | // Append title and message 106 | isString(title) && title.length && append(notificationEl, createParagraph('ncf-title')(title)); 107 | isString(message) && message.length && append(notificationEl, createParagraph('nfc-message')(message)); 108 | 109 | // Append to container 110 | append(container, notificationEl); 111 | 112 | // Remove element after duration 113 | if(options.showDuration && options.showDuration > 0) { 114 | const timeout = setTimeout(() => { 115 | container.removeChild(notificationEl); 116 | 117 | // Remove container if empty 118 | if(container.querySelectorAll('.ncf').length === 0) { 119 | document.body.removeChild(container); 120 | } 121 | }, options.showDuration); 122 | 123 | // If close on click is enabled and the user clicks, cancel timeout 124 | if(options.closeOnClick || options.displayCloseButton) { 125 | notificationEl.addEventListener('click', () => clearTimeout(timeout)); 126 | } 127 | } 128 | }; 129 | } 130 | 131 | function createNotificationContainer(position) { 132 | let container = document.querySelector(`.${position}`); 133 | 134 | if(!container) { 135 | container = createElement('div', 'ncf-container', position); 136 | append(document.body, container); 137 | } 138 | 139 | return container; 140 | } 141 | 142 | // Add Notifications to window to make globally accessible 143 | if (window.createNotification) { 144 | console.warn('Window already contains a create notification function. Have you included the script twice?'); 145 | } else { 146 | window.createNotification = createNotification; 147 | } 148 | })(window); 149 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/src/polyfills/classList.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof window.Element === 'undefined' || 'classList' in document.documentElement) return; 3 | 4 | var prototype = Array.prototype, 5 | push = prototype.push, 6 | splice = prototype.splice, 7 | join = prototype.join; 8 | 9 | function DOMTokenList(el) { 10 | this.el = el; 11 | // The className needs to be trimmed and split on whitespace 12 | // to retrieve a list of classes. 13 | var classes = el.className.replace(/^\s+|\s+$/g,'').split(/\s+/); 14 | for (var i = 0; i < classes.length; i++) { 15 | push.call(this, classes[i]); 16 | } 17 | } 18 | 19 | DOMTokenList.prototype = { 20 | add: function(token) { 21 | if(this.contains(token)) return; 22 | push.call(this, token); 23 | this.el.className = this.toString(); 24 | }, 25 | contains: function(token) { 26 | return this.el.className.indexOf(token) != -1; 27 | }, 28 | item: function(index) { 29 | return this[index] || null; 30 | }, 31 | remove: function(token) { 32 | if (!this.contains(token)) return; 33 | for (var i = 0; i < this.length; i++) { 34 | if (this[i] == token) break; 35 | } 36 | splice.call(this, i, 1); 37 | this.el.className = this.toString(); 38 | }, 39 | toString: function() { 40 | return join.call(this, ' '); 41 | }, 42 | toggle: function(token) { 43 | if (!this.contains(token)) { 44 | this.add(token); 45 | } else { 46 | this.remove(token); 47 | } 48 | 49 | return this.contains(token); 50 | } 51 | }; 52 | 53 | window.DOMTokenList = DOMTokenList; 54 | 55 | function defineElementGetter (obj, prop, getter) { 56 | if (Object.defineProperty) { 57 | Object.defineProperty(obj, prop,{ 58 | get : getter 59 | }); 60 | } else { 61 | obj.__defineGetter__(prop, getter); 62 | } 63 | } 64 | 65 | defineElementGetter(Element.prototype, 'classList', function () { 66 | return new DOMTokenList(this); 67 | }); 68 | })(); -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/src/style.scss: -------------------------------------------------------------------------------- 1 | // Base colors 2 | $success: #51A351; 3 | $info: #2F96B4; 4 | $warning: #f87400; 5 | $error: #BD362F; 6 | $grey: #999999; 7 | 8 | .ncf-container { 9 | font-size: 14px; 10 | box-sizing: border-box; 11 | position: fixed; 12 | z-index: 999999; 13 | 14 | &.nfc-top-left { 15 | top: 12px; 16 | left: 12px; 17 | } 18 | 19 | &.nfc-top-right { 20 | top: 12px; 21 | right: 12px; 22 | } 23 | 24 | &.nfc-bottom-right { 25 | bottom: 12px; 26 | right: 12px; 27 | } 28 | 29 | &.nfc-bottom-left { 30 | bottom: 12px; 31 | left: 12px; 32 | } 33 | 34 | @media (max-width: 767px) { 35 | left: 0; 36 | right: 0; 37 | } 38 | 39 | .ncf { 40 | background: #ffffff; 41 | transition: .3s ease; 42 | position: relative; 43 | pointer-events: auto; 44 | overflow: hidden; 45 | margin: 0 0 6px; 46 | padding: 30px; 47 | width: 300px; 48 | border-radius: 3px 3px 3px 3px; 49 | box-shadow: 0 0 12px $grey; 50 | color: #000000; 51 | opacity: 0.9; 52 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90); 53 | filter: alpha(opacity=90); 54 | background-position: 15px center !important; 55 | background-repeat: no-repeat !important; 56 | 57 | // Prevent annoying text selection 58 | -webkit-user-select: none; /* Chrome all / Safari all */ 59 | -moz-user-select: none; /* Firefox all */ 60 | -ms-user-select: none; /* IE 10+ */ 61 | user-select: none; /* Likely future */ 62 | 63 | &:hover { 64 | box-shadow: 0 0 12px #000000; 65 | opacity: 1; 66 | cursor: pointer; 67 | } 68 | 69 | .ncf-title { 70 | font-weight: bold; 71 | font-size: 16px; 72 | text-align: left; 73 | margin-top: 0; 74 | margin-bottom: 6px; 75 | word-wrap: break-word; 76 | } 77 | 78 | .nfc-message { 79 | margin: 0; 80 | text-align: left; 81 | word-wrap: break-word; 82 | } 83 | } 84 | 85 | // Themes 86 | .success { 87 | background: $success; 88 | color: #ffffff; 89 | padding: 15px 15px 15px 50px; 90 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg=="); 91 | } 92 | 93 | .info { 94 | background: $info; 95 | color: #ffffff; 96 | padding: 15px 15px 15px 50px; 97 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII="); 98 | } 99 | 100 | .warning { 101 | background: $warning; 102 | color: #ffffff; 103 | padding: 15px 15px 15px 50px; 104 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII="); 105 | } 106 | 107 | .error { 108 | background: $error; 109 | color: #ffffff; 110 | padding: 15px 15px 15px 50px; 111 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 112 | } 113 | 114 | button { 115 | position: relative; 116 | right: -0.3em; 117 | top: -0.3em; 118 | float: right; 119 | font-weight: bold; 120 | color: #FFFFFF; 121 | text-shadow: 0 1px 0 #ffffff; 122 | opacity: 0.8; 123 | line-height: 1; 124 | font-size: 16px; 125 | padding: 0; 126 | cursor: pointer; 127 | background: transparent; 128 | border: 0; 129 | 130 | &:hover { 131 | opacity: 1; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /examples/SmartSwitch/data_src/js_css_src/styled-notifications/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | const extractSass = new ExtractTextPlugin({ 5 | filename: 'notifications.css', 6 | disable: process.env.NODE_ENV === 'development' 7 | }); 8 | 9 | module.exports = { 10 | entry: ['./src/index.js', './src/style.scss'], 11 | output: { 12 | path: __dirname + '/dist', 13 | filename: 'notifications.js' 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.js$/, 19 | loader: 'babel-loader', 20 | query: { 21 | presets: ['babel-preset-es2015', 'es2015-ie'] 22 | } 23 | }, 24 | { 25 | test: /\.scss$/, 26 | use: extractSass.extract({ 27 | use: [{ 28 | loader: 'css-loader' 29 | }, { 30 | loader: 'sass-loader' 31 | }], 32 | // use style-loader in development 33 | fallback: 'style-loader' 34 | }) 35 | } 36 | ], 37 | }, 38 | plugins: [ 39 | extractSass 40 | ] 41 | }; -------------------------------------------------------------------------------- /examples/regex_patterns/.test.build_flags: -------------------------------------------------------------------------------- 1 | -DASYNCWEBSERVER_REGEX=1 2 | -------------------------------------------------------------------------------- /examples/regex_patterns/regex_patterns.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation with regex routes: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | // Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support 9 | 10 | // For platformio: platformio.ini: 11 | // build_flags = 12 | // -DASYNCWEBSERVER_REGEX 13 | 14 | // For arduino IDE: create/update platform.local.txt 15 | // Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt 16 | // Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt 17 | // 18 | // compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1 19 | 20 | #include 21 | #ifdef ESP32 22 | #include 23 | #include 24 | #elif defined(ESP8266) 25 | #include 26 | #include 27 | #endif 28 | #include 29 | 30 | AsyncWebServer server(80); 31 | 32 | const char* ssid = "YOUR_SSID"; 33 | const char* password = "YOUR_PASSWORD"; 34 | 35 | const char* PARAM_MESSAGE = "message"; 36 | 37 | void notFound(AsyncWebServerRequest *request) { 38 | request->send(404, "text/plain", "Not found"); 39 | } 40 | 41 | void setup() { 42 | 43 | Serial.begin(115200); 44 | WiFi.mode(WIFI_STA); 45 | WiFi.begin(ssid, password); 46 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 47 | Serial.printf("WiFi Failed!\n"); 48 | return; 49 | } 50 | 51 | Serial.print("IP Address: "); 52 | Serial.println(WiFi.localIP()); 53 | 54 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 55 | request->send(200, "text/plain", "Hello, world"); 56 | }); 57 | 58 | // Send a GET request to /sensor/ 59 | server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 60 | String sensorNumber = request->pathArg(0); 61 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); 62 | }); 63 | 64 | // Send a GET request to /sensor//action/ 65 | server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { 66 | String sensorNumber = request->pathArg(0); 67 | String action = request->pathArg(1); 68 | request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); 69 | }); 70 | 71 | server.onNotFound(notFound); 72 | 73 | server.begin(); 74 | } 75 | 76 | void loop() { 77 | } 78 | -------------------------------------------------------------------------------- /examples/simple_server/simple_server.ino: -------------------------------------------------------------------------------- 1 | // 2 | // A simple server implementation showing how to: 3 | // * serve static messages 4 | // * read GET and POST parameters 5 | // * handle missing pages / 404s 6 | // 7 | 8 | #include 9 | #ifdef ESP32 10 | #include 11 | #include 12 | #elif defined(ESP8266) 13 | #include 14 | #include 15 | #endif 16 | #include 17 | 18 | AsyncWebServer server(80); 19 | 20 | const char* ssid = "YOUR_SSID"; 21 | const char* password = "YOUR_PASSWORD"; 22 | 23 | const char* PARAM_MESSAGE = "message"; 24 | 25 | void notFound(AsyncWebServerRequest *request) { 26 | request->send(404, "text/plain", "Not found"); 27 | } 28 | 29 | void setup() { 30 | 31 | Serial.begin(115200); 32 | WiFi.mode(WIFI_STA); 33 | WiFi.begin(ssid, password); 34 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 35 | Serial.printf("WiFi Failed!\n"); 36 | return; 37 | } 38 | 39 | Serial.print("IP Address: "); 40 | Serial.println(WiFi.localIP()); 41 | 42 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 43 | request->send(200, "text/plain", "Hello, world"); 44 | }); 45 | 46 | // Send a GET request to /get?message= 47 | server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { 48 | String message; 49 | if (request->hasParam(PARAM_MESSAGE)) { 50 | message = request->getParam(PARAM_MESSAGE)->value(); 51 | } else { 52 | message = "No message sent"; 53 | } 54 | request->send(200, "text/plain", "Hello, GET: " + message); 55 | }); 56 | 57 | // Send a POST request to /post with a form field message set to 58 | server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ 59 | String message; 60 | if (request->hasParam(PARAM_MESSAGE, true)) { 61 | message = request->getParam(PARAM_MESSAGE, true)->value(); 62 | } else { 63 | message = "No message sent"; 64 | } 65 | request->send(200, "text/plain", "Hello, POST: " + message); 66 | }); 67 | 68 | server.onNotFound(notFound); 69 | 70 | server.begin(); 71 | } 72 | 73 | void loop() { 74 | } -------------------------------------------------------------------------------- /extras/README.md: -------------------------------------------------------------------------------- 1 | ### Extras 2 | Additions to facilitate code modifications (for MS Win, similar can be done on Linux) 3 | 4 | - **ehg.c (ehg.exe):** Tool to generate C-code array from file' bytes 5 | Based on [bin2array](https://github.com/TheLivingOne/bin2array/) PROGMEM keyword can optionally be added. 6 | 7 | - **rehg.c (rehg.exe):** Tool to reverse C-code array generated by **ehg.exe** back to a file 8 | Based on [c2bin](https://github.com/birkett/cbintools/tree/master/c2bin) 9 | First 4 lines of source are ignored, then parses the 0xHH - formated bytes 10 | until a } is found on separate new line. 11 | 12 | ### Tools 13 | - [TCC : Tiny C Compiler](https://bellard.org/tcc/) for **ehg** and **rehg** compiling on MS Win 14 | - [7-Zip](https://www.7-zip.org) Install 7z and use the included gzip as command line tool 15 | - [Node.js](https://nodejs.org) Install Node with default settings, then run: 16 | 17 | ``` npm install html-minifier-terser -g, npm install -g github-files-fetcher ``` 18 | 19 | ### Batch files provided 20 | - **do_emb.bat:** Generates **edit.htm.gz.h** file for embedding to **SPIFFSEditor.cpp** as binary C array - 21 | You need to comment **#define EDFS** at **SPIFFSEditor.cpp** for this choice 22 | - **do_ed_fs.bat:** Alternatively, makes a gzip-ed **edit.htm** file for fs - 23 | uncomment **#define EDFS** for this choice. Saves about 4k of program flash storage. 24 | - **undo.bat:** Reverts **edit.htm** from C array header to file (still minified!) 25 | - **update_ace.bat:** Updates **acefull.js.gz** file from latest GitHub Ace sources 26 | -------------------------------------------------------------------------------- /extras/do_ed_fs.bat: -------------------------------------------------------------------------------- 1 | copy ..\src\edit.htm edit_src.htm 2 | call html-minifier-terser --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --minify-css true --minify-js true -o edit.htm edit_src.htm 3 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 edit.htm.gz edit.htm 4 | copy edit.htm.gz ..\examples\SmartSwitch\data\edit_gz 5 | copy edit.htm.gz ..\examples\ESP_AsyncFSBrowser\data\edit_gz 6 | ehg edit.htm.gz PROGMEM 7 | copy edit.htm.gz.h ..\src\edit.htm.gz.h 8 | pause 9 | del edit.htm edit.htm.gz edit.htm.gz.h edit_src.htm -------------------------------------------------------------------------------- /extras/do_emb.bat: -------------------------------------------------------------------------------- 1 | copy ..\src\edit.htm edit_src.htm 2 | call html-minifier-terser --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --minify-css true --minify-js true -o edit.htm edit_src.htm 3 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 edit.htm.gz edit.htm 4 | del ..\examples\SmartSwitch\data\edit.htm 5 | del ..\examples\ESP_AsyncFSBrowser\data\edit.htm 6 | ehg edit.htm.gz PROGMEM 7 | copy edit.htm.gz.h ..\src\edit.htm.gz.h 8 | pause 9 | del edit.htm edit.htm.gz edit.htm.gz.h edit_src.htm -------------------------------------------------------------------------------- /extras/ehg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple, but very fast converter of file to C++ array; written in old school C. 3 | * ehg.c (ehg.exe) 4 | * Based on https://github.com/TheLivingOne/bin2array/ 5 | * by (C) Sergey A. Galin, 2019, sergey.galin@gmail.com, sergey.galin@yandex.ru 6 | * and compiled with TynyCC https://bellard.org/tcc/ 7 | * This file is a Public Domain. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | char* replace_char(char* str, char find, char replace){ 15 | char *current_pos = strchr(str,find); 16 | for (char* p = current_pos; (current_pos = strchr(str, find)) != NULL; *current_pos = replace); 17 | return str; 18 | } 19 | 20 | int main(int argc, char * argv[]) 21 | { 22 | if ((argc > 3)||(argc < 2)) { 23 | printf("USAGE: %s [PROGMEM]\n", argv[0]); 24 | return 1; 25 | } 26 | 27 | const char * in = argv[1]; 28 | const char * pr = argv[2]; 29 | char pr_o[8] = " "; 30 | 31 | if (argv[2]) sprintf(pr_o, "%s", pr); 32 | 33 | // Hello stack overflow :) 34 | char out_cpp[4096]; 35 | char usname[4096]; 36 | sprintf(usname, "%s", in); 37 | sprintf(out_cpp, "%s.h", in); 38 | 39 | replace_char(usname,'.', '_'); 40 | 41 | printf("Input: %s, output: %s\n", in, out_cpp); 42 | 43 | // 44 | // Working with the input file 45 | // 46 | FILE * fin = fopen(in, "rb"); 47 | if (!fin) { 48 | printf("Error opening input file!\n"); 49 | return 2; 50 | } 51 | fseek(fin, 0, SEEK_END); 52 | size_t size = (size_t)ftell(fin); 53 | fseek(fin, 0, SEEK_SET); 54 | printf("Input data size: %ld\n", (long)size); 55 | 56 | unsigned char * data = malloc(size); 57 | if (fread(data, size, 1, fin) != 1) { 58 | printf("Failed to read input file!\n"); 59 | free(data); 60 | fclose(fin); 61 | return 2; 62 | } 63 | fclose(fin); 64 | 65 | unsigned char arr[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; 66 | unsigned char * text = malloc(size * 6); 67 | unsigned char * out_ptr = text; 68 | unsigned char * in_ptr = data; 69 | for (size_t i = 1, col = 1; i <= size; i++, in_ptr++) { 70 | unsigned char x = *in_ptr; 71 | unsigned char y = (x & 0xF0) >> 4; 72 | unsigned char z = (x & 0x0F); 73 | 74 | *out_ptr++ = '0'; 75 | *out_ptr++ = 'x'; 76 | 77 | *out_ptr++ = arr[y]; 78 | *out_ptr++ = arr[z]; 79 | 80 | if (i != size) { 81 | *out_ptr++ = ','; 82 | } 83 | if (col == 20) { 84 | *out_ptr++ = '\n'; 85 | col = 1; 86 | } else 87 | col++; 88 | } 89 | free(data); 90 | // *out_ptr = 0; not necessary as we're using fwrite() 91 | 92 | // 93 | // Writing output file 94 | // 95 | 96 | FILE * fout = fopen(out_cpp, "wb"); 97 | if (!fout) { 98 | printf("Error opening output file!\n"); 99 | free(text); 100 | return 2; 101 | } 102 | fprintf( 103 | fout, 104 | "\n//File: %s, Size: %ld\n" 105 | "#define %s_len %ld\n" 106 | "const uint8_t %s[] %s = {\n", 107 | in, 108 | (long)size, 109 | usname, 110 | (long)size, 111 | usname, 112 | pr_o); 113 | if (fwrite(text, out_ptr - text, 1, fout) != 1) { 114 | printf("Error writing output file!"); 115 | free(text); 116 | fclose(fout); 117 | return 2; 118 | } 119 | fprintf(fout, "\n};\n"); 120 | fclose(fout); 121 | free(text); 122 | return 0; 123 | } 124 | 125 | -------------------------------------------------------------------------------- /extras/ehg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/extras/ehg.exe -------------------------------------------------------------------------------- /extras/rehg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014-2015 Anthony Birkett 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | /* 26 | * rehg.c (rehg.exe): Tool to reverse C-code array to file converted by ehg.exe 27 | * Based on https://github.com/birkett/cbintools/tree/master/c2bin 28 | * and compiled with TynyCC https://bellard.org/tcc/ 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #define BUFFER_SIZE 500 36 | 37 | int main(int argc, char *argv[]) 38 | { 39 | FILE *inputFile = NULL; 40 | FILE *outputFile = NULL; 41 | char sBuffer[BUFFER_SIZE]; 42 | char *pch; 43 | char *newline, *endBracket; 44 | int i; 45 | 46 | if (argc != 3) 47 | { 48 | printf("%s %s %s\n", "Usage:", argv[0], "
"); 49 | return 1; 50 | } 51 | 52 | inputFile = fopen(argv[1], "r"); 53 | 54 | if (inputFile == NULL) 55 | { 56 | printf("%s %s\n", "Unable to open input header:", argv[1]); 57 | return 1; 58 | } 59 | 60 | outputFile = fopen(argv[2], "wb"); 61 | 62 | if (outputFile == NULL) 63 | { 64 | printf("%s %s\n", "Unable to open output file:", argv[2]); 65 | return 1; 66 | } 67 | 68 | // Skip the first 4 lines. 69 | for (i = 0; i < 4; i++) 70 | { 71 | fgets(sBuffer, BUFFER_SIZE, inputFile); 72 | } 73 | 74 | // Get the contents of each line of the array. 75 | while (fgets(sBuffer, BUFFER_SIZE, inputFile)) 76 | { 77 | // Get rid of the new line character. 78 | newline = strchr(sBuffer, '\n'); 79 | if (newline) 80 | { 81 | *newline = 0; 82 | } 83 | 84 | // Skip this line if its the closing "};". 85 | endBracket = strchr(sBuffer, '}'); 86 | if (endBracket) 87 | { 88 | continue; 89 | } 90 | 91 | // Write out each character. 92 | pch = strtok(sBuffer, ","); 93 | while (pch != NULL) 94 | { 95 | fprintf(outputFile, "%c", strtol(pch, NULL, 0)); // autodetect 96 | pch = strtok(NULL, ","); 97 | } 98 | } 99 | 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /extras/rehg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/extras/rehg.exe -------------------------------------------------------------------------------- /extras/tmp1/placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorol/ESPAsyncWebServer/8c77d0e63f55160953fda843baa11435b05ae0bd/extras/tmp1/placeholder -------------------------------------------------------------------------------- /extras/undo.bat: -------------------------------------------------------------------------------- 1 | copy ..\src\edit.htm.gz.h edit.htm.gz.h 2 | rehg edit.htm.gz.h edit.htm.gz 3 | "C:\Program Files\7-Zip\7z.exe" e -tgzip edit.htm.gz 4 | pause 5 | -------------------------------------------------------------------------------- /extras/update_ace.bat: -------------------------------------------------------------------------------- 1 | REM https://www.npmjs.com/package/github-files-fetcher 2 | REM npm install -g github-files-fetcher 3 | REM fetcher --url=resource_url --out=output_directory 4 | 5 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/ace.js" --out=tmp1 6 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/mode-html.js" --out=tmp1 7 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/mode-json.js" --out=tmp1 8 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/theme-monokai.js" --out=tmp1 9 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/ext-searchbox.js" --out=tmp1 10 | 11 | REM if you don't need worker(s), modify line#446 of edit.htm .setUseWorker(!0) to (!1) (true to false) 12 | REM and do not take and include them below 13 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/worker-html.js" --out=tmp1 14 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/worker-css.js" --out=tmp1 15 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/worker-javascript.js" --out=tmp1 16 | call fetcher --url="https://github.com/ajaxorg/ace-builds/blob/master/src-min-noconflict/worker-json.js" --out=tmp1 17 | 18 | cd tmp1 19 | type ace.js mode-html.js mode-json.js theme-monokai.js ext-searchbox.js > acefull.js 20 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 acefull.js.gz acefull.js 21 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 worker-html.js.gz worker-html.js 22 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 worker-javascript.js.gz worker-javascript.js 23 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 worker-json.js.gz worker-json.js 24 | "C:\Program Files\7-Zip\7z.exe" a -tgzip -mx9 worker-css.js.gz worker-css.js 25 | 26 | REM update SmartSwitch /data: 27 | pause 28 | copy acefull.js.gz ..\..\examples\SmartSwitch\data\acefull.js.gz 29 | copy worker-html.js.gz ..\..\examples\SmartSwitch\data\worker-html.js.gz 30 | copy worker-javascript.js.gz ..\..\examples\SmartSwitch\data\worker-javascript.js.gz 31 | copy worker-json.js.gz ..\..\examples\SmartSwitch\data\worker-json.js.gz 32 | copy worker-css.js.gz ..\..\examples\SmartSwitch\data\worker-css.js.gz 33 | 34 | REM update ESP_AsyncFSBrowser /data: 35 | pause 36 | copy acefull.js.gz ..\..\examples\ESP_AsyncFSBrowser\data\acefull.js.gz 37 | copy worker-html.js.gz ..\..\examples\ESP_AsyncFSBrowser\data\worker-html.js.gz 38 | copy worker-javascript.js.gz ..\..\examples\ESP_AsyncFSBrowser\data\worker-javascript.js.gz 39 | copy worker-json.js.gz ..\..\examples\ESP_AsyncFSBrowser\data\worker-json.js.gz 40 | copy worker-css.js.gz ..\..\examples\ESP_AsyncFSBrowser\data\worker-css.js.gz 41 | 42 | REM delete temporary stuff 43 | pause 44 | del *.js *.gz 45 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | JsonArray KEYWORD1 2 | add KEYWORD2 3 | createArray KEYWORD3 4 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"ESP Async WebServer", 3 | "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32", 4 | "keywords":"http,async,websocket,webserver", 5 | "authors": 6 | [ 7 | { 8 | "name": "Hristo Gochkov", 9 | "url": "https://github.com/me-no-dev" 10 | }, 11 | { 12 | "name": "lorol", 13 | "url": "https://github.com/lorol", 14 | "maintainer": true 15 | } 16 | ], 17 | "repository": 18 | { 19 | "type": "git", 20 | "url": "https://github.com/lorol/ESPAsyncWebServer.git" 21 | }, 22 | "version": "1.2.4", 23 | "license": "LGPL-3.0", 24 | "frameworks": "arduino", 25 | "platforms": ["espressif8266", "espressif32"], 26 | "dependencies": [ 27 | { 28 | "name": "ESPAsyncTCP", 29 | "platforms": "espressif8266" 30 | }, 31 | { 32 | "name": "AsyncTCP", 33 | "platforms": "espressif32" 34 | }, 35 | { 36 | "name": "Hash", 37 | "platforms": "espressif8266" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP Async WebServer 2 | version=1.2.4 3 | author=Me-No-Dev 4 | maintainer=lorol 5 | sentence=Async Web Server for ESP8266 and ESP32 6 | paragraph=Async Web Server for ESP8266 and ESP32 7 | category=Other 8 | url=https://github.com/lorol/ESPAsyncWebServer 9 | architectures=esp8266,esp32 10 | -------------------------------------------------------------------------------- /src/AsyncEventSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | #ifndef ASYNCEVENTSOURCE_H_ 21 | #define ASYNCEVENTSOURCE_H_ 22 | 23 | #include 24 | #ifdef ESP32 25 | #include 26 | #define SSE_MAX_QUEUED_MESSAGES 32 27 | #else 28 | #include 29 | #define SSE_MAX_QUEUED_MESSAGES 8 30 | #endif 31 | #include 32 | 33 | #include "AsyncWebSynchronization.h" 34 | 35 | #ifdef ESP8266 36 | #include 37 | #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library 38 | #include <../src/Hash.h> 39 | #endif 40 | #endif 41 | 42 | #ifdef ESP32 43 | #define DEFAULT_MAX_SSE_CLIENTS 8 44 | #else 45 | #define DEFAULT_MAX_SSE_CLIENTS 4 46 | #endif 47 | 48 | class AsyncEventSource; 49 | class AsyncEventSourceResponse; 50 | class AsyncEventSourceClient; 51 | typedef std::function ArEventHandlerFunction; 52 | typedef std::function ArAuthorizeConnectHandler; 53 | 54 | class AsyncEventSourceMessage { 55 | private: 56 | uint8_t * _data; 57 | size_t _len; 58 | size_t _sent; 59 | //size_t _ack; 60 | size_t _acked; 61 | public: 62 | AsyncEventSourceMessage(const char * data, size_t len); 63 | ~AsyncEventSourceMessage(); 64 | size_t ack(size_t len, uint32_t time __attribute__((unused))); 65 | size_t send(AsyncClient *client); 66 | bool finished(){ return _acked == _len; } 67 | bool sent() { return _sent == _len; } 68 | }; 69 | 70 | class AsyncEventSourceClient { 71 | private: 72 | AsyncClient *_client; 73 | AsyncEventSource *_server; 74 | uint32_t _lastId; 75 | LinkedList _messageQueue; 76 | void _queueMessage(AsyncEventSourceMessage *dataMessage); 77 | void _runQueue(); 78 | 79 | public: 80 | 81 | AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); 82 | ~AsyncEventSourceClient(); 83 | 84 | AsyncClient* client(){ return _client; } 85 | void close(); 86 | void write(const char * message, size_t len); 87 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 88 | bool connected() const { return (_client != NULL) && _client->connected(); } 89 | uint32_t lastId() const { return _lastId; } 90 | size_t packetsWaiting() const { return _messageQueue.length(); } 91 | 92 | //system callbacks (do not call) 93 | void _onAck(size_t len, uint32_t time); 94 | void _onPoll(); 95 | void _onTimeout(uint32_t time); 96 | void _onDisconnect(); 97 | }; 98 | 99 | class AsyncEventSource: public AsyncWebHandler { 100 | private: 101 | String _url; 102 | LinkedList _clients; 103 | ArEventHandlerFunction _connectcb; 104 | ArAuthorizeConnectHandler _authorizeConnectHandler; 105 | public: 106 | AsyncEventSource(const String& url); 107 | ~AsyncEventSource(); 108 | 109 | const char * url() const { return _url.c_str(); } 110 | void close(); 111 | void onConnect(ArEventHandlerFunction cb); 112 | void authorizeConnect(ArAuthorizeConnectHandler cb); 113 | void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); 114 | size_t count() const; //number clinets connected 115 | size_t avgPacketsWaiting() const; 116 | 117 | //system callbacks (do not call) 118 | void _addClient(AsyncEventSourceClient * client); 119 | void _handleDisconnect(AsyncEventSourceClient * client); 120 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 121 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 122 | }; 123 | 124 | class AsyncEventSourceResponse: public AsyncWebServerResponse { 125 | private: 126 | String _content; 127 | AsyncEventSource *_server; 128 | public: 129 | AsyncEventSourceResponse(AsyncEventSource *server); 130 | void _respond(AsyncWebServerRequest *request); 131 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 132 | bool _sourceValid() const { return true; } 133 | }; 134 | 135 | 136 | #endif /* ASYNCEVENTSOURCE_H_ */ 137 | -------------------------------------------------------------------------------- /src/AsyncWebSynchronization.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNCWEBSYNCHRONIZATION_H_ 2 | #define ASYNCWEBSYNCHRONIZATION_H_ 3 | 4 | // Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default 5 | 6 | #include 7 | 8 | #ifdef ESP32 9 | 10 | // This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore 11 | class AsyncWebLock 12 | { 13 | private: 14 | SemaphoreHandle_t _lock; 15 | mutable void *_lockedBy; 16 | 17 | public: 18 | AsyncWebLock() { 19 | _lock = xSemaphoreCreateBinary(); 20 | _lockedBy = NULL; 21 | xSemaphoreGive(_lock); 22 | } 23 | 24 | ~AsyncWebLock() { 25 | vSemaphoreDelete(_lock); 26 | } 27 | 28 | bool lock() const { 29 | extern void *pxCurrentTCB; 30 | if (_lockedBy != pxCurrentTCB) { 31 | xSemaphoreTake(_lock, portMAX_DELAY); 32 | _lockedBy = pxCurrentTCB; 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | void unlock() const { 39 | _lockedBy = NULL; 40 | xSemaphoreGive(_lock); 41 | } 42 | }; 43 | 44 | #else 45 | 46 | // This is the 8266 version of the Sync Lock which is currently unimplemented 47 | class AsyncWebLock 48 | { 49 | 50 | public: 51 | AsyncWebLock() { 52 | } 53 | 54 | ~AsyncWebLock() { 55 | } 56 | 57 | bool lock() const { 58 | return false; 59 | } 60 | 61 | void unlock() const { 62 | } 63 | }; 64 | #endif 65 | 66 | class AsyncWebLockGuard 67 | { 68 | private: 69 | const AsyncWebLock *_lock; 70 | 71 | public: 72 | AsyncWebLockGuard(const AsyncWebLock &l) { 73 | if (l.lock()) { 74 | _lock = &l; 75 | } else { 76 | _lock = NULL; 77 | } 78 | } 79 | 80 | ~AsyncWebLockGuard() { 81 | if (_lock) { 82 | _lock->unlock(); 83 | } 84 | } 85 | }; 86 | 87 | #endif // ASYNCWEBSYNCHRONIZATION_H_ -------------------------------------------------------------------------------- /src/SPIFFSEditor.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIFFSEditor_H_ 2 | #define SPIFFSEditor_H_ 3 | #include 4 | 5 | class SPIFFSEditor: public AsyncWebHandler { 6 | private: 7 | fs::FS _fs; 8 | String _username; 9 | String _password; 10 | bool _authenticated; 11 | uint32_t _startTime; 12 | public: 13 | #ifdef ESP32 14 | SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); 15 | #else 16 | //SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); 17 | SPIFFSEditor(const String& username, const String& password, const fs::FS& fs); // do not show warning that SPIFFS has been deprecated 18 | #endif 19 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 20 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 21 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; 22 | virtual bool isRequestHandlerTrivial() override final {return false;} 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/StringArray.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef STRINGARRAY_H_ 22 | #define STRINGARRAY_H_ 23 | 24 | #include "stddef.h" 25 | #include "WString.h" 26 | 27 | template 28 | class LinkedListNode { 29 | T _value; 30 | public: 31 | LinkedListNode* next; 32 | LinkedListNode(const T val): _value(val), next(nullptr) {} 33 | ~LinkedListNode(){} 34 | const T& value() const { return _value; }; 35 | T& value(){ return _value; } 36 | }; 37 | 38 | template class Item = LinkedListNode> 39 | class LinkedList { 40 | public: 41 | typedef Item ItemType; 42 | typedef std::function OnRemove; 43 | typedef std::function Predicate; 44 | private: 45 | ItemType* _root; 46 | OnRemove _onRemove; 47 | 48 | class Iterator { 49 | ItemType* _node; 50 | public: 51 | Iterator(ItemType* current = nullptr) : _node(current) {} 52 | Iterator(const Iterator& i) : _node(i._node) {} 53 | Iterator& operator ++() { _node = _node->next; return *this; } 54 | bool operator != (const Iterator& i) const { return _node != i._node; } 55 | const T& operator * () const { return _node->value(); } 56 | const T* operator -> () const { return &_node->value(); } 57 | }; 58 | 59 | public: 60 | typedef const Iterator ConstIterator; 61 | ConstIterator begin() const { return ConstIterator(_root); } 62 | ConstIterator end() const { return ConstIterator(nullptr); } 63 | 64 | LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} 65 | ~LinkedList(){} 66 | void add(const T& t){ 67 | auto it = new ItemType(t); 68 | if(!_root){ 69 | _root = it; 70 | } else { 71 | auto i = _root; 72 | while(i->next) i = i->next; 73 | i->next = it; 74 | } 75 | } 76 | T& front() const { 77 | return _root->value(); 78 | } 79 | 80 | bool isEmpty() const { 81 | return _root == nullptr; 82 | } 83 | size_t length() const { 84 | size_t i = 0; 85 | auto it = _root; 86 | while(it){ 87 | i++; 88 | it = it->next; 89 | } 90 | return i; 91 | } 92 | size_t count_if(Predicate predicate) const { 93 | size_t i = 0; 94 | auto it = _root; 95 | while(it){ 96 | if (!predicate){ 97 | i++; 98 | } 99 | else if (predicate(it->value())) { 100 | i++; 101 | } 102 | it = it->next; 103 | } 104 | return i; 105 | } 106 | const T* nth(size_t N) const { 107 | size_t i = 0; 108 | auto it = _root; 109 | while(it){ 110 | if(i++ == N) 111 | return &(it->value()); 112 | it = it->next; 113 | } 114 | return nullptr; 115 | } 116 | bool remove(const T& t){ 117 | auto it = _root; 118 | auto pit = _root; 119 | while(it){ 120 | if(it->value() == t){ 121 | if(it == _root){ 122 | _root = _root->next; 123 | } else { 124 | pit->next = it->next; 125 | } 126 | 127 | if (_onRemove) { 128 | _onRemove(it->value()); 129 | } 130 | 131 | delete it; 132 | return true; 133 | } 134 | pit = it; 135 | it = it->next; 136 | } 137 | return false; 138 | } 139 | bool remove_first(Predicate predicate){ 140 | auto it = _root; 141 | auto pit = _root; 142 | while(it){ 143 | if(predicate(it->value())){ 144 | if(it == _root){ 145 | _root = _root->next; 146 | } else { 147 | pit->next = it->next; 148 | } 149 | if (_onRemove) { 150 | _onRemove(it->value()); 151 | } 152 | delete it; 153 | return true; 154 | } 155 | pit = it; 156 | it = it->next; 157 | } 158 | return false; 159 | } 160 | 161 | void free(){ 162 | while(_root != nullptr){ 163 | auto it = _root; 164 | _root = _root->next; 165 | if (_onRemove) { 166 | _onRemove(it->value()); 167 | } 168 | delete it; 169 | } 170 | _root = nullptr; 171 | } 172 | }; 173 | 174 | 175 | class StringArray : public LinkedList { 176 | public: 177 | 178 | StringArray() : LinkedList(nullptr) {} 179 | 180 | bool containsIgnoreCase(const String& str){ 181 | for (const auto& s : *this) { 182 | if (str.equalsIgnoreCase(s)) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | }; 189 | 190 | 191 | 192 | 193 | #endif /* STRINGARRAY_H_ */ 194 | -------------------------------------------------------------------------------- /src/WebAuthentication.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "WebAuthentication.h" 22 | #include 23 | #ifdef ESP32 24 | #include "mbedtls/md5.h" 25 | #else 26 | #include "md5.h" 27 | #endif 28 | 29 | 30 | // Basic Auth hash = base64("username:password") 31 | 32 | bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ 33 | if(username == NULL || password == NULL || hash == NULL) 34 | return false; 35 | 36 | size_t toencodeLen = strlen(username)+strlen(password)+1; 37 | size_t encodedLen = base64_encode_expected_len(toencodeLen); 38 | if(strlen(hash) != encodedLen) 39 | // Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 40 | #ifdef ARDUINO_ARCH_ESP32 41 | if(strlen(hash) != encodedLen) 42 | #else 43 | if (strlen(hash) != encodedLen - 1) 44 | #endif 45 | return false; 46 | 47 | char *toencode = new char[toencodeLen+1]; 48 | if(toencode == NULL){ 49 | return false; 50 | } 51 | char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; 52 | if(encoded == NULL){ 53 | delete[] toencode; 54 | return false; 55 | } 56 | sprintf_P(toencode, PSTR("%s:%s"), username, password); 57 | if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ 58 | delete[] toencode; 59 | delete[] encoded; 60 | return true; 61 | } 62 | delete[] toencode; 63 | delete[] encoded; 64 | return false; 65 | } 66 | 67 | static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more 68 | #ifdef ESP32 69 | mbedtls_md5_context _ctx; 70 | #else 71 | md5_context_t _ctx; 72 | #endif 73 | uint8_t i; 74 | uint8_t * _buf = (uint8_t*)malloc(16); 75 | if(_buf == NULL) 76 | return false; 77 | memset(_buf, 0x00, 16); 78 | #ifdef ESP32 79 | mbedtls_md5_init(&_ctx); 80 | mbedtls_md5_starts(&_ctx); 81 | mbedtls_md5_update(&_ctx, data, len); 82 | mbedtls_md5_finish(&_ctx, _buf); 83 | #else 84 | MD5Init(&_ctx); 85 | MD5Update(&_ctx, data, len); 86 | MD5Final(_buf, &_ctx); 87 | #endif 88 | for(i = 0; i < 16; i++) { 89 | sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); 90 | } 91 | free(_buf); 92 | return true; 93 | } 94 | 95 | static String genRandomMD5(){ 96 | #ifdef ESP8266 97 | uint32_t r = RANDOM_REG32; 98 | #else 99 | uint32_t r = rand(); 100 | #endif 101 | char * out = (char*)malloc(33); 102 | if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) 103 | return emptyString; 104 | String res = String(out); 105 | free(out); 106 | return res; 107 | } 108 | 109 | static String stringMD5(const String& in){ 110 | char * out = (char*)malloc(33); 111 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 112 | return emptyString; 113 | String res = String(out); 114 | free(out); 115 | return res; 116 | } 117 | 118 | String generateDigestHash(const char * username, const char * password, const char * realm){ 119 | if(username == NULL || password == NULL || realm == NULL){ 120 | return emptyString; 121 | } 122 | char * out = (char*)malloc(33); 123 | String res = String(username); 124 | res += ':'; 125 | res.concat(realm); 126 | res += ':'; 127 | String in = res; 128 | in.concat(password); 129 | if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) 130 | return emptyString; 131 | res.concat(out); 132 | free(out); 133 | return res; 134 | } 135 | 136 | String requestDigestAuthentication(const char * realm){ 137 | String header = F("realm=\""); 138 | if(realm == NULL) 139 | header.concat(F("asyncesp")); 140 | else 141 | header.concat(realm); 142 | header.concat(F("\", qop=\"auth\", nonce=\"")); 143 | header.concat(genRandomMD5()); 144 | header.concat(F("\", opaque=\"")); 145 | header.concat(genRandomMD5()); 146 | header += '"'; 147 | return header; 148 | } 149 | 150 | bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ 151 | if(username == NULL || password == NULL || header == NULL || method == NULL){ 152 | //os_printf("AUTH FAIL: missing requred fields\n"); 153 | return false; 154 | } 155 | 156 | String myHeader = String(header); 157 | int nextBreak = myHeader.indexOf(','); 158 | if(nextBreak < 0){ 159 | //os_printf("AUTH FAIL: no variables\n"); 160 | return false; 161 | } 162 | 163 | String myUsername = String(); 164 | String myRealm = String(); 165 | String myNonce = String(); 166 | String myUri = String(); 167 | String myResponse = String(); 168 | String myQop = String(); 169 | String myNc = String(); 170 | String myCnonce = String(); 171 | 172 | myHeader += F(", "); 173 | do { 174 | String avLine = myHeader.substring(0, nextBreak); 175 | avLine.trim(); 176 | myHeader = myHeader.substring(nextBreak+1); 177 | nextBreak = myHeader.indexOf(','); 178 | 179 | int eqSign = avLine.indexOf('='); 180 | if(eqSign < 0){ 181 | //os_printf("AUTH FAIL: no = sign\n"); 182 | return false; 183 | } 184 | String varName = avLine.substring(0, eqSign); 185 | avLine = avLine.substring(eqSign + 1); 186 | if(avLine.startsWith(String('"'))){ 187 | avLine = avLine.substring(1, avLine.length() - 1); 188 | } 189 | 190 | if(varName.equals(F("username"))){ 191 | if(!avLine.equals(username)){ 192 | //os_printf("AUTH FAIL: username\n"); 193 | return false; 194 | } 195 | myUsername = avLine; 196 | } else if(varName.equals(F("realm"))){ 197 | if(realm != NULL && !avLine.equals(realm)){ 198 | //os_printf("AUTH FAIL: realm\n"); 199 | return false; 200 | } 201 | myRealm = avLine; 202 | } else if(varName.equals(F("nonce"))){ 203 | if(nonce != NULL && !avLine.equals(nonce)){ 204 | //os_printf("AUTH FAIL: nonce\n"); 205 | return false; 206 | } 207 | myNonce = avLine; 208 | } else if(varName.equals(F("opaque"))){ 209 | if(opaque != NULL && !avLine.equals(opaque)){ 210 | //os_printf("AUTH FAIL: opaque\n"); 211 | return false; 212 | } 213 | } else if(varName.equals(F("uri"))){ 214 | if(uri != NULL && !avLine.equals(uri)){ 215 | //os_printf("AUTH FAIL: uri\n"); 216 | return false; 217 | } 218 | myUri = avLine; 219 | } else if(varName.equals(F("response"))){ 220 | myResponse = avLine; 221 | } else if(varName.equals(F("qop"))){ 222 | myQop = avLine; 223 | } else if(varName.equals(F("nc"))){ 224 | myNc = avLine; 225 | } else if(varName.equals(F("cnonce"))){ 226 | myCnonce = avLine; 227 | } 228 | } while(nextBreak > 0); 229 | 230 | String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); 231 | String ha2 = String(method) + ':' + myUri; 232 | String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); 233 | 234 | if(myResponse.equals(stringMD5(response))){ 235 | //os_printf("AUTH SUCCESS\n"); 236 | return true; 237 | } 238 | 239 | //os_printf("AUTH FAIL: password\n"); 240 | return false; 241 | } 242 | -------------------------------------------------------------------------------- /src/WebAuthentication.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifndef WEB_AUTHENTICATION_H_ 23 | #define WEB_AUTHENTICATION_H_ 24 | 25 | #include "Arduino.h" 26 | 27 | bool checkBasicAuthentication(const char * header, const char * username, const char * password); 28 | String requestDigestAuthentication(const char * realm); 29 | bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); 30 | 31 | //for storing hashed versions on the device that can be authenticated against 32 | String generateDigestHash(const char * username, const char * password, const char * realm); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/WebHandlerImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERHANDLERIMPL_H_ 22 | #define ASYNCWEBSERVERHANDLERIMPL_H_ 23 | 24 | #include 25 | #ifdef ASYNCWEBSERVER_REGEX 26 | #include 27 | #endif 28 | 29 | #include "stddef.h" 30 | #include 31 | 32 | class AsyncStaticWebHandler: public AsyncWebHandler { 33 | using File = fs::File; 34 | using FS = fs::FS; 35 | private: 36 | bool _getFile(AsyncWebServerRequest *request); 37 | bool _fileExists(AsyncWebServerRequest *request, const String& path); 38 | uint8_t _countBits(const uint8_t value) const; 39 | protected: 40 | FS _fs; 41 | String _uri; 42 | String _path; 43 | String _default_file; 44 | String _cache_control; 45 | String _last_modified; 46 | AwsTemplateProcessor _callback; 47 | bool _isDir; 48 | bool _gzipFirst; 49 | uint8_t _gzipStats; 50 | public: 51 | AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); 52 | virtual bool canHandle(AsyncWebServerRequest *request) override final; 53 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 54 | AsyncStaticWebHandler& setIsDir(bool isDir); 55 | AsyncStaticWebHandler& setDefaultFile(const char* filename); 56 | AsyncStaticWebHandler& setCacheControl(const char* cache_control); 57 | AsyncStaticWebHandler& setLastModified(const char* last_modified); 58 | AsyncStaticWebHandler& setLastModified(struct tm* last_modified); 59 | #ifdef ESP8266 60 | AsyncStaticWebHandler& setLastModified(time_t last_modified); 61 | AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated 62 | #endif 63 | AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} 64 | }; 65 | 66 | class AsyncCallbackWebHandler: public AsyncWebHandler { 67 | private: 68 | protected: 69 | String _uri; 70 | WebRequestMethodComposite _method; 71 | ArRequestHandlerFunction _onRequest; 72 | ArUploadHandlerFunction _onUpload; 73 | ArBodyHandlerFunction _onBody; 74 | bool _isRegex; 75 | public: 76 | AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} 77 | void setUri(const String& uri){ 78 | _uri = uri; 79 | _isRegex = uri.startsWith("^") && uri.endsWith("$"); 80 | } 81 | void setMethod(WebRequestMethodComposite method){ _method = method; } 82 | void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } 83 | void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } 84 | void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } 85 | 86 | virtual bool canHandle(AsyncWebServerRequest *request) override final{ 87 | 88 | if(!_onRequest) 89 | return false; 90 | 91 | if(!(_method & request->method())) 92 | return false; 93 | 94 | #ifdef ASYNCWEBSERVER_REGEX 95 | if (_isRegex) { 96 | std::regex pattern(_uri.c_str()); 97 | std::smatch matches; 98 | std::string s(request->url().c_str()); 99 | if(std::regex_search(s, matches, pattern)) { 100 | for (size_t i = 1; i < matches.size(); ++i) { // start from 1 101 | request->_addPathParam(matches[i].str().c_str()); 102 | } 103 | } else { 104 | return false; 105 | } 106 | } else 107 | #endif 108 | if (_uri.length() && _uri.startsWith("/*.")) { 109 | String uriTemplate = String (_uri); 110 | uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); 111 | if (!request->url().endsWith(uriTemplate)) 112 | return false; 113 | } 114 | else 115 | if (_uri.length() && _uri.endsWith("*")) { 116 | String uriTemplate = String(_uri); 117 | uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); 118 | if (!request->url().startsWith(uriTemplate)) 119 | return false; 120 | } 121 | else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) 122 | return false; 123 | 124 | request->addInterestingHeader("ANY"); 125 | return true; 126 | } 127 | 128 | virtual void handleRequest(AsyncWebServerRequest *request) override final { 129 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 130 | return request->requestAuthentication(); 131 | if(_onRequest) 132 | _onRequest(request); 133 | else 134 | request->send(500); 135 | } 136 | virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { 137 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 138 | return request->requestAuthentication(); 139 | if(_onUpload) 140 | _onUpload(request, filename, index, data, len, final); 141 | } 142 | virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { 143 | if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) 144 | return request->requestAuthentication(); 145 | if(_onBody) 146 | _onBody(request, data, len, index, total); 147 | } 148 | virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} 149 | }; 150 | 151 | #endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ 152 | -------------------------------------------------------------------------------- /src/WebResponseImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ 22 | #define ASYNCWEBSERVERRESPONSEIMPL_H_ 23 | 24 | #ifdef Arduino_h 25 | // arduino is not compatible with std::vector 26 | #undef min 27 | #undef max 28 | #endif 29 | #include 30 | // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. 31 | 32 | class AsyncBasicResponse: public AsyncWebServerResponse { 33 | private: 34 | String _content; 35 | public: 36 | AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); 37 | void _respond(AsyncWebServerRequest *request); 38 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 39 | bool _sourceValid() const { return true; } 40 | }; 41 | 42 | class AsyncAbstractResponse: public AsyncWebServerResponse { 43 | private: 44 | String _head; 45 | // Data is inserted into cache at begin(). 46 | // This is inefficient with vector, but if we use some other container, 47 | // we won't be able to access it as contiguous array of bytes when reading from it, 48 | // so by gaining performance in one place, we'll lose it in another. 49 | std::vector _cache; 50 | size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); 51 | size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); 52 | protected: 53 | AwsTemplateProcessor _callback; 54 | public: 55 | AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); 56 | void _respond(AsyncWebServerRequest *request); 57 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 58 | bool _sourceValid() const { return false; } 59 | virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } 60 | }; 61 | 62 | #ifndef TEMPLATE_PLACEHOLDER 63 | #define TEMPLATE_PLACEHOLDER '%' 64 | #endif 65 | 66 | #define TEMPLATE_PARAM_NAME_LENGTH 32 67 | class AsyncFileResponse: public AsyncAbstractResponse { 68 | using File = fs::File; 69 | using FS = fs::FS; 70 | private: 71 | File _content; 72 | String _path; 73 | void _setContentType(const String& path); 74 | public: 75 | AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 76 | AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); 77 | ~AsyncFileResponse(); 78 | bool _sourceValid() const { return !!(_content); } 79 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 80 | }; 81 | 82 | class AsyncStreamResponse: public AsyncAbstractResponse { 83 | private: 84 | Stream *_content; 85 | public: 86 | AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); 87 | bool _sourceValid() const { return !!(_content); } 88 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 89 | }; 90 | 91 | class AsyncCallbackResponse: public AsyncAbstractResponse { 92 | private: 93 | AwsResponseFiller _content; 94 | size_t _filledLength; 95 | public: 96 | AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 97 | bool _sourceValid() const { return !!(_content); } 98 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 99 | }; 100 | 101 | class AsyncChunkedResponse: public AsyncAbstractResponse { 102 | private: 103 | AwsResponseFiller _content; 104 | size_t _filledLength; 105 | public: 106 | AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); 107 | bool _sourceValid() const { return !!(_content); } 108 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 109 | }; 110 | 111 | class AsyncProgmemResponse: public AsyncAbstractResponse { 112 | private: 113 | const uint8_t * _content; 114 | size_t _readLength; 115 | public: 116 | AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); 117 | bool _sourceValid() const { return true; } 118 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 119 | }; 120 | 121 | class cbuf; 122 | 123 | class AsyncResponseStream: public AsyncAbstractResponse, public Print { 124 | private: 125 | cbuf *_content; 126 | public: 127 | AsyncResponseStream(const String& contentType, size_t bufferSize); 128 | ~AsyncResponseStream(); 129 | bool _sourceValid() const { return (_state < RESPONSE_END); } 130 | virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; 131 | size_t write(const uint8_t *data, size_t len); 132 | size_t write(uint8_t data); 133 | using Print::write; 134 | }; 135 | 136 | #endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ 137 | -------------------------------------------------------------------------------- /src/WebServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous WebServer library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | #include "ESPAsyncWebServer.h" 22 | #include "WebHandlerImpl.h" 23 | 24 | bool ON_STA_FILTER(AsyncWebServerRequest *request) { 25 | return WiFi.localIP() == request->client()->localIP(); 26 | } 27 | 28 | bool ON_AP_FILTER(AsyncWebServerRequest *request) { 29 | return WiFi.localIP() != request->client()->localIP(); 30 | } 31 | 32 | #ifndef HAVE_FS_FILE_OPEN_MODE 33 | const char *fs::FileOpenMode::read = "r"; 34 | const char *fs::FileOpenMode::write = "w"; 35 | const char *fs::FileOpenMode::append = "a"; 36 | #endif 37 | 38 | AsyncWebServer::AsyncWebServer(uint16_t port) 39 | : _server(port) 40 | , _rewrites(LinkedList([](AsyncWebRewrite* r){ delete r; })) 41 | , _handlers(LinkedList([](AsyncWebHandler* h){ delete h; })) 42 | { 43 | _catchAllHandler = new AsyncCallbackWebHandler(); 44 | if(_catchAllHandler == NULL) 45 | return; 46 | _server.onClient([](void *s, AsyncClient* c){ 47 | if(c == NULL) 48 | return; 49 | c->setRxTimeout(3); 50 | AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); 51 | if(r == NULL){ 52 | c->close(true); 53 | c->free(); 54 | delete c; 55 | } 56 | }, this); 57 | } 58 | 59 | AsyncWebServer::~AsyncWebServer(){ 60 | reset(); 61 | end(); 62 | if(_catchAllHandler) delete _catchAllHandler; 63 | } 64 | 65 | AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ 66 | _rewrites.add(rewrite); 67 | return *rewrite; 68 | } 69 | 70 | bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ 71 | return _rewrites.remove(rewrite); 72 | } 73 | 74 | AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ 75 | return addRewrite(new AsyncWebRewrite(from, to)); 76 | } 77 | 78 | AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ 79 | _handlers.add(handler); 80 | return *handler; 81 | } 82 | 83 | bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ 84 | return _handlers.remove(handler); 85 | } 86 | 87 | void AsyncWebServer::begin(){ 88 | _server.setNoDelay(true); 89 | _server.begin(); 90 | } 91 | 92 | void AsyncWebServer::end(){ 93 | _server.end(); 94 | } 95 | 96 | #if ASYNC_TCP_SSL_ENABLED 97 | void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ 98 | _server.onSslFileRequest(cb, arg); 99 | } 100 | 101 | void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ 102 | _server.beginSecure(cert, key, password); 103 | } 104 | #endif 105 | 106 | void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ 107 | delete request; 108 | } 109 | 110 | void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ 111 | for(const auto& r: _rewrites){ 112 | if (r->match(request)){ 113 | request->_url = r->toUrl(); 114 | request->_addGetParams(r->params()); 115 | } 116 | } 117 | } 118 | 119 | void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ 120 | for(const auto& h: _handlers){ 121 | if (h->filter(request) && h->canHandle(request)){ 122 | request->setHandler(h); 123 | return; 124 | } 125 | } 126 | 127 | request->addInterestingHeader(F("ANY")); 128 | request->setHandler(_catchAllHandler); 129 | } 130 | 131 | 132 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ 133 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 134 | handler->setUri(uri); 135 | handler->setMethod(method); 136 | handler->onRequest(onRequest); 137 | handler->onUpload(onUpload); 138 | handler->onBody(onBody); 139 | addHandler(handler); 140 | return *handler; 141 | } 142 | 143 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ 144 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 145 | handler->setUri(uri); 146 | handler->setMethod(method); 147 | handler->onRequest(onRequest); 148 | handler->onUpload(onUpload); 149 | addHandler(handler); 150 | return *handler; 151 | } 152 | 153 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ 154 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 155 | handler->setUri(uri); 156 | handler->setMethod(method); 157 | handler->onRequest(onRequest); 158 | addHandler(handler); 159 | return *handler; 160 | } 161 | 162 | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ 163 | AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); 164 | handler->setUri(uri); 165 | handler->onRequest(onRequest); 166 | addHandler(handler); 167 | return *handler; 168 | } 169 | 170 | AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ 171 | AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); 172 | addHandler(handler); 173 | return *handler; 174 | } 175 | 176 | void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ 177 | _catchAllHandler->onRequest(fn); 178 | } 179 | 180 | void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ 181 | _catchAllHandler->onUpload(fn); 182 | } 183 | 184 | void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ 185 | _catchAllHandler->onBody(fn); 186 | } 187 | 188 | void AsyncWebServer::reset(){ 189 | _rewrites.free(); 190 | _handlers.free(); 191 | 192 | if (_catchAllHandler != NULL){ 193 | _catchAllHandler->onRequest(NULL); 194 | _catchAllHandler->onUpload(NULL); 195 | _catchAllHandler->onBody(NULL); 196 | } 197 | } 198 | 199 | --------------------------------------------------------------------------------