mirror of
https://github.com/me-no-dev/ESPAsyncWebServer.git
synced 2025-09-30 08:10:56 +02:00
Cookie Authentication including WS part taken from ayushsharma82 ideas
https://github.com/me-no-dev/ESPAsyncWebServer/pull/684 For Websocket added: void handleHandshake(AwsHandshakeHandler handler) For EventSource added: void authorizeConnect(ArAuthorizeConnectHandler cb); Auth example and modifications. Tested on ESP8266 and ESP32 platforms See SmartSwitch.ino
This commit is contained in:
@@ -4,14 +4,16 @@
|
||||
|
||||
## SmartSwitch
|
||||
* Remote Temperature Control application with schedule (example car block heater or battery charger)
|
||||
* Based on ESP_AsyncFSBrowser example
|
||||
* Based on ESP_AsyncFSBrowser example with ACE editor
|
||||
* Wide browser compatibility, no extra server-side needed
|
||||
* HTTP server and WebSocket, single port
|
||||
* Standalone, no JS dependencies for the browser from Internet (I hope), ace editor included
|
||||
* Added ESPAsyncWiFiManager
|
||||
* Fallback to an own WIFI_AP, no Internet to sync but by a browser the clock can be set once
|
||||
* Real Time (NTP) w/ Time Zones
|
||||
* Memorized settings to EEPROM
|
||||
* Multiple clients can be connected at same time, they see each other' requests
|
||||
* Base Authentication of the editor, static content, WS
|
||||
* Or Cookie Authentication including WS part, need lib src changes taken from https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
|
||||
* Default credentials <b>smart:switch</b>
|
||||
* Use latest ESP8266 or ESP32 core(from GitHub)
|
||||
* Use latest ESP8266 ESP32 cores from GitHub
|
||||
|
||||
|
@@ -17,8 +17,20 @@ Use latest ESP core lib (from Github)
|
||||
//#define DEL_WFM // delete Wifi credentials stored
|
||||
//(use once then comment and flash again), also HTTP /erase-wifi can do the same live
|
||||
|
||||
#define USE_AUTH_STAT // .setAuthentication also for static (editor always requires auth)
|
||||
//#define USE_AUTH_WS // .setAuthentication also for ws, broken for Safari iOS
|
||||
// AUTH COOKIE uses only the password, Base uses both
|
||||
#define http_username "smart"
|
||||
#define http_password "switch"
|
||||
|
||||
//See https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
|
||||
#define USE_AUTH_COOKIE
|
||||
#define MY_COOKIE_FULL "LLKQ=7;max-age=31536000;"
|
||||
#define MY_COOKIE_DEL "LLKQ="
|
||||
#define MY_COOKIE "LLKQ=7"
|
||||
|
||||
#ifndef USE_AUTH_COOKIE
|
||||
#define USE_AUTH_STAT //Base Auth for stat, /commands and SPIFFSEditor
|
||||
//#define USE_AUTH_WS //Base Auth also for WS, not very supported
|
||||
#endif
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#ifdef ESP32
|
||||
@@ -57,6 +69,9 @@ Use latest ESP core lib (from Github)
|
||||
#define DHTTYPE DHT22 // DHT 11 // DHT 22, AM2302, AM2321 // DHT 21, AM2301
|
||||
#define DHTPIN 4 //D2
|
||||
|
||||
#define DHT_T_CORR -0.5 //Temperature offset compensation of the sensor (can be -)
|
||||
#define DHT_H_CORR 1.5 //Humidity offset compensation of the sensor
|
||||
|
||||
DHT dht(DHTPIN, DHTTYPE);
|
||||
|
||||
// SKETCH BEGIN MAIN DECLARATIONS
|
||||
@@ -80,9 +95,7 @@ AsyncWebSocket ws("/ws");
|
||||
const char* ssid = "MYROUTERSSD";
|
||||
const char* password = "MYROUTERPASSWD";
|
||||
#endif
|
||||
const char* hostName = "smartsw";
|
||||
const char* http_username = "smart";
|
||||
const char* http_password = "switch";
|
||||
const char* hostName = "smartsw32";
|
||||
|
||||
// RTC
|
||||
static timeval tv;
|
||||
@@ -91,6 +104,15 @@ static time_t now;
|
||||
// HW I/O
|
||||
const int btnPin = 0; //D3
|
||||
const int ledPin = 2; //D4
|
||||
|
||||
#ifdef ESP32
|
||||
#define LED_ON 0x1
|
||||
#define LED_OFF 0x0
|
||||
#elif defined(ESP8266)
|
||||
#define LED_ON 0x0
|
||||
#define LED_OFF 0x1
|
||||
#endif
|
||||
|
||||
int btnState = HIGH;
|
||||
|
||||
// Globals
|
||||
@@ -101,7 +123,7 @@ float t = 0;
|
||||
float h = 0;
|
||||
bool udht = false;
|
||||
bool heat_enabled_prev = false;
|
||||
int ledState;
|
||||
int ledState = LED_OFF;
|
||||
|
||||
struct EE_bl {
|
||||
byte memid; //here goes the EEMARK stamp
|
||||
@@ -178,29 +200,31 @@ void showTime()
|
||||
}
|
||||
|
||||
if (heat_enabled_prev) { // smart control (delayed one cycle)
|
||||
if (((t - HYST) < ee.tempe)&&(ledState == HIGH)) { // OFF->ON once
|
||||
ledState = LOW;
|
||||
if (((t - HYST) < ee.tempe)&&(ledState == LED_OFF)) { // OFF->ON once
|
||||
ledState = LED_ON;
|
||||
digitalWrite(ledPin, ledState); // apply change
|
||||
ws.textAll("led,ledon");
|
||||
}
|
||||
if ((((t + HYST) > ee.tempe)&&(ledState == LOW))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
|
||||
ledState = HIGH;
|
||||
if ((((t + HYST) > ee.tempe)&&(ledState == LED_ON))||(!heat_enabled)) { // ON->OFF once, also turn off at end of period.
|
||||
ledState = LED_OFF;
|
||||
digitalWrite(ledPin, ledState); // apply change
|
||||
ws.textAll("led,ledoff");
|
||||
}
|
||||
Serial.printf(ledState ? "LED OFF" : "LED ON");
|
||||
|
||||
Serial.printf(ledState == LED_ON ? "LED ON" : "LED OFF");
|
||||
Serial.print(F(", Smart enabled\n"));
|
||||
}
|
||||
heat_enabled_prev = heat_enabled; //update
|
||||
}
|
||||
|
||||
void updateDHT(){
|
||||
h = dht.readHumidity();
|
||||
t = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
|
||||
if (isnan(h) || isnan(t)) {
|
||||
float h1 = dht.readHumidity();
|
||||
float t1 = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
|
||||
if (isnan(h1) || isnan(t1)) {
|
||||
Serial.print(F("Failed to read from DHT sensor!"));
|
||||
h = 0; // debug w/o sensor
|
||||
t = 0;
|
||||
} else {
|
||||
h = h1 + DHT_H_CORR;
|
||||
t = t1 + DHT_T_CORR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +240,7 @@ void checkPhysicalButton()
|
||||
if (btnState != LOW) { // btnState is used to avoid sequential toggles
|
||||
ledState = !ledState;
|
||||
digitalWrite(ledPin, ledState);
|
||||
if (ledState) ws.textAll("led,ledoff");
|
||||
if (ledState == LED_OFF) ws.textAll("led,ledoff");
|
||||
else ws.textAll("led,ledon");
|
||||
}
|
||||
btnState = LOW;
|
||||
@@ -241,6 +265,16 @@ void mytimer() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_AUTH_COOKIE
|
||||
bool myHandshake(AsyncWebServerRequest *request){ // false will 401
|
||||
if (request->hasHeader("Cookie")){
|
||||
String cookie = request->header("Cookie");
|
||||
if (cookie.indexOf(MY_COOKIE) != -1) return true;
|
||||
else return false;
|
||||
} else return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// server
|
||||
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
|
||||
if(type == WS_EVT_CONNECT){
|
||||
@@ -252,7 +286,7 @@ void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventT
|
||||
Serial.printf("[%u] Connected from %d.%d.%d.%d\n", client->id(), ip[0], ip[1], ip[2], ip[3]);
|
||||
showTime();
|
||||
analogSample();
|
||||
if (ledState) ws.textAll("led,ledoff");
|
||||
if (ledState == LED_OFF) ws.textAll("led,ledoff");
|
||||
else ws.textAll("led,ledon");
|
||||
|
||||
ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);
|
||||
@@ -279,11 +313,11 @@ void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventT
|
||||
}
|
||||
if(data[0] == 'L') { // LED
|
||||
if(data[1] == '1') {
|
||||
ledState = LOW;
|
||||
ledState = LED_ON;
|
||||
ws.textAll("led,ledon"); // for others
|
||||
}
|
||||
else if(data[1] == '0') {
|
||||
ledState = HIGH;
|
||||
ledState = LED_OFF;
|
||||
ws.textAll("led,ledoff");
|
||||
}
|
||||
digitalWrite(ledPin, ledState); // apply change
|
||||
@@ -444,31 +478,74 @@ void setup(){
|
||||
#ifdef USE_AUTH_WS
|
||||
ws.setAuthentication(http_username,http_password);
|
||||
#endif
|
||||
|
||||
#ifdef USE_AUTH_COOKIE
|
||||
ws.handleHandshake(myHandshake);
|
||||
#endif
|
||||
|
||||
ws.onEvent(onWsEvent);
|
||||
server.addHandler(&ws);
|
||||
|
||||
#ifdef ESP32
|
||||
#ifdef USE_AUTH_STAT
|
||||
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password));
|
||||
#elif defined(USE_AUTH_COOKIE)
|
||||
server.addHandler(new SPIFFSEditor(SPIFFS)).setFilter(myHandshake);
|
||||
#endif
|
||||
#elif defined(ESP8266)
|
||||
#ifdef USE_AUTH_STAT
|
||||
server.addHandler(new SPIFFSEditor(http_username,http_password));
|
||||
#elif defined(USE_AUTH_COOKIE)
|
||||
server.addHandler(new SPIFFSEditor()).setFilter(myHandshake);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){ // direct request->answer
|
||||
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||
#ifdef USE_AUTH_COOKIE
|
||||
server.on("/lg2n", HTTP_POST, [](AsyncWebServerRequest *request){ // cookie test
|
||||
if((request->hasParam("pa2w",true) && (String(request->getParam("pa2w",true)->value().c_str()) == String(http_password)))||(request->hasParam("lg0f",true))){
|
||||
AsyncWebServerResponse *response = request->beginResponse(301);
|
||||
response->addHeader("Location", "/");
|
||||
response->addHeader("Cache-Control", "no-cache");
|
||||
if(request->hasParam("lg0f",true)) response->addHeader("Set-Cookie", MY_COOKIE_DEL);
|
||||
else response->addHeader("Set-Cookie", MY_COOKIE_FULL);
|
||||
request->send(response);
|
||||
} else request->send(200, "text/plain","Wrong Password!");
|
||||
});
|
||||
#endif
|
||||
|
||||
// below paths need individual auth ////////////////////////////////////////////////
|
||||
|
||||
server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){ // direct request->answer
|
||||
#ifdef USE_AUTH_STAT
|
||||
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
|
||||
#endif
|
||||
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||
#ifdef USE_AUTH COOKIE
|
||||
}).setFilter(myHandshake);
|
||||
#else
|
||||
});
|
||||
#endif
|
||||
|
||||
server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
#ifdef USE_AUTH_STAT
|
||||
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
|
||||
#endif
|
||||
if(request->hasParam("btime")){
|
||||
time_t rtc = (request->getParam("btime")->value()).toInt();
|
||||
timeval tv = { rtc, 0 };
|
||||
settimeofday(&tv, nullptr);
|
||||
}
|
||||
request->send(200, "text/plain","Got browser time ...");
|
||||
#ifdef USE_AUTH COOKIE
|
||||
}).setFilter(myHandshake);
|
||||
#else
|
||||
});
|
||||
|
||||
#endif
|
||||
|
||||
server.on("/hw-reset", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
#ifdef USE_AUTH_STAT
|
||||
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
|
||||
#endif
|
||||
request->onDisconnect([]() {
|
||||
#ifdef ESP32
|
||||
ESP.restart();
|
||||
@@ -477,9 +554,16 @@ void setup(){
|
||||
#endif
|
||||
});
|
||||
request->send(200, "text/plain","Restarting ...");
|
||||
#ifdef USE_AUTH COOKIE
|
||||
}).setFilter(myHandshake);
|
||||
#else
|
||||
});
|
||||
#endif
|
||||
|
||||
server.on("/erase-wifi", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
#ifdef USE_AUTH_STAT
|
||||
if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
|
||||
#endif
|
||||
request->onDisconnect([]() {
|
||||
WiFi.disconnect(true);
|
||||
#ifdef ESP32
|
||||
@@ -489,12 +573,23 @@ void setup(){
|
||||
#endif
|
||||
});
|
||||
request->send(200, "text/plain","Erasing WiFi data ...");
|
||||
});
|
||||
|
||||
#ifdef USE_AUTH_STAT
|
||||
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
|
||||
#ifdef USE_AUTH COOKIE
|
||||
}).setFilter(myHandshake);
|
||||
#else
|
||||
});
|
||||
#endif
|
||||
|
||||
// above paths need individual auth ////////////////////////////////////////////////
|
||||
|
||||
#ifdef USE_AUTH_COOKIE
|
||||
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
|
||||
server.serveStatic("/", SPIFFS, "/login/").setDefaultFile("index.htm").setFilter(!myHandshake);
|
||||
#else
|
||||
#ifdef USE_AUTH_STAT
|
||||
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
|
||||
#else
|
||||
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
server.onNotFound([](AsyncWebServerRequest *request){ // nothing known
|
||||
|
Binary file not shown.
BIN
examples/SmartSwitch/data/login/favicon.ico.gz
Normal file
BIN
examples/SmartSwitch/data/login/favicon.ico.gz
Normal file
Binary file not shown.
20
examples/SmartSwitch/data/login/index.htm
Normal file
20
examples/SmartSwitch/data/login/index.htm
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Login</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||
</head>
|
||||
<body style="background-color:#bbb;font-family:arial;"><center>
|
||||
<br><br>
|
||||
<h4>Password</h4>
|
||||
<form action="/lg2n" method="post">
|
||||
<input type="password" name="pa2w">
|
||||
<input type="checkbox" name="lg0f"><label for="lg0f">Logoff</label><br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form></center>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -263,6 +263,10 @@ void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){
|
||||
_authorizeConnectHandler = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
||||
/*char * temp = (char *)malloc(2054);
|
||||
if(temp != NULL){
|
||||
@@ -333,6 +337,7 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||
return false;
|
||||
}
|
||||
request->addInterestingHeader(F("Last-Event-ID"));
|
||||
request->addInterestingHeader("Cookie");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -340,6 +345,11 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
if(_authorizeConnectHandler != NULL){
|
||||
if(!_authorizeConnectHandler(request)){
|
||||
return request->send(401);
|
||||
}
|
||||
}
|
||||
request->send(new AsyncEventSourceResponse(this));
|
||||
}
|
||||
|
||||
|
@@ -49,6 +49,7 @@ class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
||||
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;
|
||||
|
||||
class AsyncEventSourceMessage {
|
||||
private:
|
||||
@@ -100,6 +101,7 @@ class AsyncEventSource: public AsyncWebHandler {
|
||||
String _url;
|
||||
LinkedList<AsyncEventSourceClient *> _clients;
|
||||
ArEventHandlerFunction _connectcb;
|
||||
ArAuthorizeConnectHandler _authorizeConnectHandler;
|
||||
public:
|
||||
AsyncEventSource(const String& url);
|
||||
~AsyncEventSource();
|
||||
@@ -107,6 +109,7 @@ class AsyncEventSource: public AsyncWebHandler {
|
||||
const char * url() const { return _url.c_str(); }
|
||||
void close();
|
||||
void onConnect(ArEventHandlerFunction cb);
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
size_t count() const; //number clinets connected
|
||||
size_t avgPacketsWaiting() const;
|
||||
|
@@ -1146,6 +1146,7 @@ void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){
|
||||
const char __WS_STR_CONNECTION[] PROGMEM = { "Connection" };
|
||||
const char __WS_STR_UPGRADE[] PROGMEM = { "Upgrade" };
|
||||
const char __WS_STR_ORIGIN[] PROGMEM = { "Origin" };
|
||||
const char __WS_STR_COOKIE[] PROGMEM = { "Cookie" };
|
||||
const char __WS_STR_VERSION[] PROGMEM = { "Sec-WebSocket-Version" };
|
||||
const char __WS_STR_KEY[] PROGMEM = { "Sec-WebSocket-Key" };
|
||||
const char __WS_STR_PROTOCOL[] PROGMEM = { "Sec-WebSocket-Protocol" };
|
||||
@@ -1155,6 +1156,7 @@ const char __WS_STR_UUID[] PROGMEM = { "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" };
|
||||
#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
|
||||
#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
|
||||
#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
|
||||
#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
|
||||
#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
|
||||
#define WS_STR_KEY FPSTR(__WS_STR_KEY)
|
||||
#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
|
||||
@@ -1171,6 +1173,7 @@ bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){
|
||||
request->addInterestingHeader(WS_STR_CONNECTION);
|
||||
request->addInterestingHeader(WS_STR_UPGRADE);
|
||||
request->addInterestingHeader(WS_STR_ORIGIN);
|
||||
request->addInterestingHeader(WS_STR_COOKIE);
|
||||
request->addInterestingHeader(WS_STR_VERSION);
|
||||
request->addInterestingHeader(WS_STR_KEY);
|
||||
request->addInterestingHeader(WS_STR_PROTOCOL);
|
||||
@@ -1185,6 +1188,14 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){
|
||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())){
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
if(_handshakeHandler != nullptr){
|
||||
if(!_handshakeHandler(request)){
|
||||
request->send(401);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
|
||||
if(version->value().toInt() != 13){
|
||||
AsyncWebServerResponse *response = request->beginResponse(400);
|
||||
|
@@ -237,6 +237,7 @@ class AsyncWebSocketClient {
|
||||
void _onData(void *pbuf, size_t plen);
|
||||
};
|
||||
|
||||
typedef std::function<bool(AsyncWebServerRequest *request)> AwsHandshakeHandler;
|
||||
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
|
||||
|
||||
//WebServer Handler implementation that plays the role of a socket server
|
||||
@@ -248,6 +249,7 @@ class AsyncWebSocket: public AsyncWebHandler {
|
||||
AsyncWebSocketClientLinkedList _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler;
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
AsyncWebLock _lock;
|
||||
|
||||
@@ -316,6 +318,11 @@ class AsyncWebSocket: public AsyncWebHandler {
|
||||
_eventHandler = handler;
|
||||
}
|
||||
|
||||
// Handshake Handler
|
||||
void handleHandshake(AwsHandshakeHandler handler){
|
||||
_handshakeHandler = handler;
|
||||
}
|
||||
|
||||
//system callbacks (do not call)
|
||||
uint32_t _getNextId(){ return _cNextId++; }
|
||||
void _addClient(AsyncWebSocketClient * client);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
//File: edit.htm.gz, Size: 4408
|
||||
#define edit_htm_gz_len 4408
|
||||
const uint8_t edit_htm_gz[] PROGMEM = {
|
||||
0x1F,0x8B,0x08,0x08,0x52,0x4A,0xB0,0x5E,0x02,0x00,0x65,0x64,0x69,0x74,0x2E,0x68,0x74,0x6D,0x00,0xB5,
|
||||
0x1F,0x8B,0x08,0x08,0x42,0x13,0xB7,0x5E,0x02,0x00,0x65,0x64,0x69,0x74,0x2E,0x68,0x74,0x6D,0x00,0xB5,
|
||||
0x1A,0x0B,0x5B,0xDB,0x36,0xF0,0xAF,0x18,0x6F,0x63,0xF6,0xE2,0x38,0x0E,0xA5,0xAC,0x73,0x30,0x2C,0x50,
|
||||
0x56,0xFA,0x02,0x4A,0x42,0x3B,0xCA,0xD8,0x3E,0xC5,0x56,0x62,0x15,0x5B,0xF6,0x2C,0x99,0x40,0xB3,0xFC,
|
||||
0xF7,0x9D,0x24,0x3F,0x43,0xE8,0x1E,0xDF,0xD6,0x6E,0x8D,0xA4,0xD3,0x9D,0xEE,0x4E,0xF7,0x54,0xB2,0xBB,
|
||||
|
Reference in New Issue
Block a user