diff --git a/.gitignore b/.gitignore index 817f7418..9741261e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ node_modules *WWWData.h lib/framework/WWWData.h ssl_certs/cacert.pem +/logs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd15951..4966073c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,37 +2,52 @@ All notable changes to this project will be documented in this file. -## [WIP] - Work in Progress +## [0.4.0] - 2024-04-21 + +This upgrade might require one minor change as `MqttPubSub.h` and its class had been renamed to `MqttEndpoint.h` and `MqttEndoint` respectively. However, it is strongly advised, that you change all existing WebSocketServer endpoints to the new event socket system. + +> [!NOTE] +> The new Event Socket system is likely to change with coming updates. ### Added - Added build flag `-D SERIAL_INFO` to platformio.ini to enable / disable all `Serial.print()` statements. On some boards with native USB those Serial prints have been reported to block and make the server unresponsive. -- Added a hook handler to StatefulService. Unlike an UPDATE a hook is called every time a state receives and updated, even if the result is UNCHANGED or ERROR. -- Added missing include for S2 in SystemStatus.cpp (#23) -- Added awareness of front end build script for all 3 major JS package managers. The script will auto-identify the package manager by the lock-file. (#40) +- Added a hook handler to StatefulService. Unlike an UPDATE a hook is called every time a state receives an updated, even if the result is UNCHANGED or ERROR. +- Added missing include for S2 in SystemStatus.cpp [#23](https://github.com/theelims/ESP32-sveltekit/issues/23) +- Added awareness of front end build script for all 3 major JS package managers. The script will auto-identify the package manager by the lock-file. [#40](https://github.com/theelims/ESP32-sveltekit/pull/40) +- Added a new event socket to bundle the websocket server and the notifications events. This saves on open sockets and allows for concurrent visitors of the internal website. The normal websocket server endpoint remains as an option, should a pure websocket connection be desired. An EventEndpoint was added to use this with Stateful Services. [#29](https://github.com/theelims/ESP32-sveltekit/issues/29) and [#43](https://github.com/theelims/ESP32-sveltekit/pull/43) +- TS Types definition in one central place for the frontend. ### Changed -- more generic board definition in platformio.ini (#20) -- refactored MqttPubSub.h into a single class to improve readability -- Moves appName and copyright to `layout.ts` to keep customization in one place (#31) -- Make eventSource use timeout for reconnect (#34) -- Make each toasts disappear after timeout (#35) +- more generic board definition in platformio.ini [#20](https://github.com/theelims/ESP32-sveltekit/pull/20) +- Renamed `MqttPubSub.h` and class to `MqttEndpoint.h` and class. +- refactored MqttEndpoint.h into a single class to improve readability +- Moves appName and copyright to `layout.ts` to keep customization in one place [#31](https://github.com/theelims/ESP32-sveltekit/pull/31) +- Make event source use timeout for reconnect [#34](https://github.com/theelims/ESP32-sveltekit/pull/34) +- Make each toasts disappear after timeout [#35](https://github.com/theelims/ESP32-sveltekit/pull/35) - Fixed version `platform = espressif32 @ 6.6.0` in platformio.ini +- Analytics data limited to 1000 data points (roughly 33 minutes). +- postcss.config.cjs as ESM module [#24](https://github.com/theelims/ESP32-sveltekit/issues/24) ### Fixed -- Duplicate method in FeatureService (#18) - Fixed compile error with FLAG `-D SERVE_CONFIG_FILES` -- Fixed typo in telemetry.ts (#38) -- Fixed the development warning: `Loading /rest/features using `window.fetch`. For best results, use the `fetch`that is passed to your`load` function:` +- Fixed typo in telemetry.ts [#38](https://github.com/theelims/ESP32-sveltekit/pull/38) +- Fixed the development warning: `Loading /rest/features using 'window.fetch'. For best results, use the 'fetch' that is passed to your 'load' function:` ### Removed +- Duplicate method in FeatureService [#18](https://github.com/theelims/ESP32-sveltekit/pull/18) - Duplicate lines in Systems Settings view. -- Removes duplicate begin (#36) +- Removes duplicate begin [#36](https://github.com/theelims/ESP32-sveltekit/pull/36) +- Temporary disabled OTA progress update due to crash with PsychicHttp [#32](https://github.com/theelims/ESP32-sveltekit/issues/32) until a fix is found. + +### Known Issues + +- On ESP32-C3 the security features should be disabled in features.ini: `-D FT_SECURITY=0`. If enabled the ESP32-C3 becomes extremely sluggish with frequent connection drops. -## [0.3.0] - 2023-02-05 +## [0.3.0] - 2024-02-05 > [!CAUTION] > This update has breaking changes! diff --git a/README.md b/README.md index 0f5c3f5c..64042c25 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ SvelteKit is ideally suited to be served from constrained devices like an ESP32. ### :telephone: Rich Communication Interfaces -Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API or WebSocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP. +Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API, a WebSocket based Event Socket and a classic Websocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP. ### :file_cabinet: WiFi Provisioning and Management diff --git a/docs/buildprocess.md b/docs/buildprocess.md index bf2d4544..a3ed5357 100644 --- a/docs/buildprocess.md +++ b/docs/buildprocess.md @@ -141,6 +141,16 @@ build_flags = -D SERVE_CONFIG_FILES ``` +### Serial Info + +In some circumstances it might be beneficial to not print any information on the serial consol (Serial1 or USB CDC). By commenting out the following build flag ESP32-Sveltekit will not print any information on the serial console. + +```ini +build_flags = +... + -D SERIAL_INFO +``` + ## SSL Root Certificate Store Some features like firmware download or the MQTT client require a SSL connection. For that the SSL Root CA certificate must be known to the ESP32. The build system contains a python script derived from Espressif ESP-IDF building a certificate store containing one or more certificates. In order to create the store you must uncomment the three lines below in `platformio.ini`. diff --git a/docs/index.md b/docs/index.md index 5c855a7d..1a152e4c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,7 +31,7 @@ SvelteKit is ideally suited to be served from constrained devices like an ESP32. ### :telephone: Rich Communication Interfaces -Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API or WebSocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP. +Comes with a rich set of communication interfaces to cover most standard needs of an IoT application. Like MQTT client, HTTP RESTful API, a WebSocket based Event Socket and a classic Websocket Server. All communication channels are stateful and fully synchronized. Changes propagate and are communicated to all other participants. The states can be persisted on the file system as well. For accurate time keeping time can by synchronized over NTP. ### :file_cabinet: WiFi Provisioning and Management diff --git a/docs/statefulservice.md b/docs/statefulservice.md index 4e15d727..5da1f4a2 100644 --- a/docs/statefulservice.md +++ b/docs/statefulservice.md @@ -54,6 +54,8 @@ class LightStateService : public StatefulService { }; ``` +### Update Handler + You may listen for changes to state by registering an update handler callback. It is possible to remove an update handler later if required. ```cpp @@ -74,9 +76,11 @@ An "originId" is passed to the update handler which may be used to identify the | Origin | Description | | -------------------------- | ----------------------------------------------- | | http | An update sent over REST (HttpEndpoint) | -| mqtt | An update sent over MQTT (MqttPubSub) | +| mqtt | An update sent over MQTT (MqttEndpoint) | | websocketserver:{clientId} | An update sent over WebSocket (WebSocketServer) | +### Hook Handler + Sometimes if can be desired to hook into every update of an state, even if the StateUpdateResult is `StateUpdateResult::UNCHANGED` and the update handler isn't called. In such cases you can use the hook handler. Similarly it can be removed later. ```cpp @@ -119,7 +123,7 @@ There are three possible return values for an update function which are as follo | StateUpdateResult::UNCHANGED | The state was unchanged, propagation should not take place | | StateUpdateResult::ERROR | There was an error updating the state, propagation should not take place | -### Serialization +### JSON Serialization When reading or updating state from an external source (HTTP, WebSockets, or MQTT for example) the state must be marshalled into a serializable form (JSON). SettingsService provides two callback patterns which facilitate this internally: @@ -165,7 +169,7 @@ JsonObject jsonObject = jsonDocument.as(); lightStateService->update(jsonObject, LightState::update, "timer"); ``` -### Endpoints +### HTTP RESTful Endpoint The framework provides an [HttpEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/HttpEndpoint.h) class which may be used to register GET and POST handlers to read and update the state over HTTP. You may construct an HttpEndpoint as a part of the StatefulService or separately if you prefer. @@ -191,7 +195,7 @@ Endpoint security is provided by authentication predicates which are [documented To register the HTTP endpoints with the web server the function `_httpEndpoint.begin()` must be called in the custom StatefulService Class' own `void begin()` function. -### Persistence +### File System Persistence [FSPersistence.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/FSPersistence.h) allows you to save state to the filesystem. FSPersistence automatically writes changes to the file system when state is updated. This feature can be disabled by calling `disableUpdateHandler()` if manual control of persistence is required. @@ -209,7 +213,33 @@ class LightStateService : public StatefulService { }; ``` -### WebSockets +### Event Socket Endpoint + +[EventEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/EventEndpoint.h) wraps the [Event Socket](#event-socket) into an endpoint compatible with a stateful service. The client may subscribe and unsubscribe to this event to receive updates or push updates to the ESP32. The current state is synchronized upon subscription. + +The code below demonstrates how to extend the LightStateService class to provide an WebSocket: + +```cpp +class LightStateService : public StatefulService { + public: + LightStateService(EventSocket *socket) : + _eventEndpoint(LightState::read, LightState::update, this, socket, "led") {} + + void begin() + { + _eventEndpoint.begin(); + } + + private: + EventEndpoint _eventEndpoint; +}; +``` + +To register the event endpoint with the event socket the function `_eventEndpoint.begin()` must be called in the custom StatefulService Class' own `void begin()` function. + +Since all events run through one websocket connection it is not possible to use the [securityManager](#security-features) to limit access to individual events. The security defaults to `AuthenticationPredicates::IS_AUTHENTICATED`. + +### WebSocket Server [WebSocketServer.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/WebSocketServer.h) allows you to read and update state over a WebSocket connection. WebSocketServer automatically pushes changes to all connected clients when state is updated. @@ -235,11 +265,11 @@ WebSocket security is provided by authentication predicates which are [documente To register the WS endpoint with the web server the function `_webSocketServer.begin()` must be called in the custom StatefulService Class' own `void begin()` function. -### MQTT +### MQTT Client The framework includes an MQTT client which can be configured via the UI. MQTT requirements will differ from project to project so the framework exposes the client for you to use as you see fit. The framework does however provide a utility to interface StatefulService to a pair of pub/sub (state/set) topics. This utility can be used to synchronize state with software such as Home Assistant. -[MqttPubSub.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/MqttPubSub.h) allows you to publish and subscribe to synchronize state over a pair of MQTT topics. MqttPubSub automatically pushes changes to the "pub" topic and reads updates from the "sub" topic. +[MqttEndpoint.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/MqttEndpoint.h) allows you to publish and subscribe to synchronize state over a pair of MQTT topics. MqttEndpoint automatically pushes changes to the "pub" topic and reads updates from the "sub" topic. The code below demonstrates how to extend the LightStateService class to interface with MQTT: @@ -248,7 +278,7 @@ The code below demonstrates how to extend the LightStateService class to interfa class LightStateService : public StatefulService { public: LightStateService(AsyncMqttClient* mqttClient) : - _mqttPubSub(LightState::read, + _mqttEndpoint(LightState::read, LightState::update, this, mqttClient, @@ -257,18 +287,68 @@ class LightStateService : public StatefulService { } private: - MqttPubSub _mqttPubSub; + MqttEndpoint _mqttEndpoint; }; ``` You can re-configure the pub/sub topics at runtime as required: ```cpp -_mqttPubSub.configureBroker("homeassistant/light/desk_lamp/set", "homeassistant/light/desk_lamp/state"); +_mqttEndpoint.configureBroker("homeassistant/light/desk_lamp/set", "homeassistant/light/desk_lamp/state"); ``` The demo project allows the user to modify the MQTT topics via the UI so they can be changed without re-flashing the firmware. +## Event Socket + +Beside RESTful HTTP Endpoints the Event Socket System provides a convenient communication path between the client and the ESP32. It uses a single WebSocket connection to synchronize state and to push realtime data to the client. The client needs to subscribe to the topics he is interested. Only clients who have an active subscription will receive data. Every authenticated client may make use of this system as the security settings are set to `AuthenticationPredicates::IS_AUTHENTICATED`. + +### Emit an Event + +The Event Socket provides an overloaded `emit()` function to push data to all subscribed clients. This is used by various esp32sveltekit classes to push real time data to the client. First an event must be registered with the Event Socket by calling `_socket.registerEvent("CustomEvent");`. Only then clients may subscribe to this custom event and you're entitled to emit event data: + +```cpp +void emit(String event, String payload); +void emit(const char *event, const char *payload); +void emit(const char *event, const char *payload, const char *originId, bool onlyToSameOrigin = false); +``` + +The latter function allowing a selection of the recipient. If `onlyToSameOrigin = false` the payload is distributed to all subscribed clients, except the `originId`. If `onlyToSameOrigin = true` only the client with `originId` will receive the payload. This is used by the [EventEndpoint](#event-socket-endpoint) to sync the initial state when a new client subscribes. + +### Receive an Event + +A callback or lambda function can be registered to receive an ArduinoJSON object and the originId of the client sending the data: + +```cpp +_socket.onEvent("CostumEvent",[&](JsonObject &root, int originId) +{ + bool ledState = root["led_on"]; +}); +``` + +### Get Notified on Subscriptions + +Similarly a callback or lambda function may be registered to get notified when a client subscribes to an event: + +```cpp +_socket.onEvent("CostumEvent",[&](const String &originId, bool sync) +{ + Serial.println("New Client subscribed: " + originId); +}); +``` + +The boolean parameter provided will always be `true`. + +### Push Notifications to All Clients + +It is possibly to send push notifications to all clients by using the Event Socket. These will be displayed as toasts an the client side. Either directly call + +```cpp +esp32sveltekit.getSocket()->pushNotification("Pushed a message!", PUSHINFO); +``` + +or keep a local pointer to the `EventSocket` instance. It is possible to send `PUSHINFO`, `PUSHWARNING`, `PUSHERROR` and `PUSHSUCCESS` events to all clients. + ## Security features The framework has security features to prevent unauthorized use of the device. This is driven by [SecurityManager.h](https://github.com/theelims/ESP32-sveltekit/blob/main/lib/framework/SecurityManager.h). @@ -397,24 +477,6 @@ esp32sveltekit.recoveryMode(); will force a start of the AP regardless of the AP settings. It will not change the the AP settings. To exit the recovery mode restart the device or change the AP settings in the UI. -### Push Notifications to All Clients - -It is possibly to send push notifications to all clients by using Server Side Events. These will be displayed as toasts an the client side. Either directly call - -```cpp -esp32sveltekit.getNotificationEvents()->pushNotification("Pushed a message!", PUSHINFO, millis()); -``` - -or keep a local pointer to the `NotificationEvents` instance. It is possible to send `PUSHINFO`, `PUSHWARNING`, `PUSHERROR` and `PUSHSUCCESS` events to all clients. The HTTP endpoint for this service is at `/events/notifications`. - -In addition the raw `send()` function is mapped out as well: - -```cpp -esp32sveltekit.getNotificationEvents()->send("Pushed a message!", "event", millis()); -``` - -This allows you to send your own Server-Sent Events without opening a new HTTP connection. - ### Power Down with Deep Sleep This API service can place the ESP32 in the lowest power deep sleep mode consuming only a few µA. It uses the EXT1 wakeup source, so the ESP32 can be woken up with a button or from a peripherals interrupt. Consult the [ESP-IDF Api Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext1_wakeup8uint64_t28esp_sleep_ext1_wakeup_mode_t) which GPIOs can be used for this. The RTC will also be powered down, so an external pull-up or pull-down resistor is required. It is not possible to persist variable state through the deep sleep. To optimize the deep sleep power consumption it is advisable to use the callback function to put pins with external pull-up's or pull-down's in a special isolated state to prevent current leakage. Please consult the [ESP-IDF Api Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#configuring-ios-deep-sleep-only) for this. @@ -441,7 +503,7 @@ esp32sveltekit.getSleepService()->sleepNow(); ### Battery State of Charge -A small helper class let's you update the battery icon in the status bar. This is useful if you have a battery operated IoT device. It must be enabled in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini). It uses Server-sent events and exposes two functions that can be used to update the clients. +A small helper class let's you update the battery icon in the status bar. This is useful if you have a battery operated IoT device. It must be enabled in [features.ini](https://github.com/theelims/ESP32-sveltekit/blob/main/features.ini). It uses the [Event Socket](#event-socket) and exposes two functions that can be used to update the clients. ```cpp esp32sveltekit.getBatteryService()->updateSOC(float stateOfCharge); // update state of charge in percent (0 - 100%) diff --git a/docs/stores.md b/docs/stores.md index 3c4b7202..07b7077d 100644 --- a/docs/stores.md +++ b/docs/stores.md @@ -22,9 +22,31 @@ In addition to the properties it provides two methods for initializing the user The user credentials including the JWT token are stored in the browsers local storage. Any javascript executed on the browser can access this making it extremely vulnerable to XSS attacks. Also the HTTP connection between ESP32 and front end is not encrypted making it possible for everyone to read the JWT token in the same network. Fixing these severe security issues is on the todo list for upcoming releases. +## Event Socket + +The [Event Socket System](statefulservice.md#event-socket) is conveniently provided as a Svelte store. Import the store, subscribe to the data interested with `socket.on`. To unsubscribe simply call `socket.off`. Data can be sent to the ESP32 by calling `socket.sendEvent` + +```ts +import { socket } from "$lib/stores/socket"; + +let lightState: LightState = { led_on: false }; + +onMount(() => { + socket.on("led", (data) => { + lightState = data; + }); +}); + +onDestroy(() => socket.off("led")); + +socket.sendEvent("led", lightState); +``` + +Subscribing to an invalid event will only create a warning in the ESP_LOG on the serial console of the ESP32. + ## Telemetry -The telemetry store can be used to update telemetry data like RSSI via Server-Sent Events. The corresponding `eventListener` functions are located in `+layout.svelte`. +The telemetry store can be used to update telemetry data like RSSI via the [Event Socket](statefulservice.md#event-socket) system. ```ts import { telemetry } from "$lib/stores/telemetry"; @@ -32,19 +54,19 @@ import { telemetry } from "$lib/stores/telemetry"; It exposes the following properties you can subscribe to: -| Property | Type | Description | -| ---------------------------------- | --------- | ------------------------------------------ | -| `$telemetry.rssi.rssi` | `Number` | The RSSI signal strengt of the WiFi in dBm | -| `$telemetry.rssi.connected` | `Boolean` | Connection status of the WiFi | -| `$telemetry.battery.soc` | `Number` | Battery state of charge | -| `$telemetry.battery.charging` | `Boolean` | Is battery connected to charger | -| `$telemetry.download_ota.status` | `String` | Status of OTA | -| `$telemetry.download_ota.progress` | `Number` | Progress of OTA | -| `$telemetry.download_ota.error` | `String` | Error Message of OTA | +| Property | Type | Description | +| ---------------------------------- | --------- | ------------------------------------------- | +| `$telemetry.rssi.rssi` | `Number` | The RSSI signal strength of the WiFi in dBm | +| `$telemetry.rssi.connected` | `Boolean` | Connection status of the WiFi | +| `$telemetry.battery.soc` | `Number` | Battery state of charge | +| `$telemetry.battery.charging` | `Boolean` | Is battery connected to charger | +| `$telemetry.download_ota.status` | `String` | Status of OTA | +| `$telemetry.download_ota.progress` | `Number` | Progress of OTA | +| `$telemetry.download_ota.error` | `String` | Error Message of OTA | ## Analytics -The analytics store holds a log of heap and other debug information via Server-Sent Events. The corresponding `eventListener` functions are located in `+layout.svelte`. +The analytics store holds a log of heap and other debug information via the [Event Socket](statefulservice.md#event-socket) system. ```ts import { analytics } from "$lib/stores/analytics"; @@ -62,4 +84,4 @@ It exposes an array of the following properties you can subscribe to: | `$analytics.fs_total` | `Number` | Total bytes of the file system | | `$analytics.core_temp` | `Number` | Core temperature (on some chips) | -By default there is one data point every 2 seconds. +By default there is one data point every 2 seconds. It holds 1000 data points worth roughly 33 Minutes of data. diff --git a/docs/sveltekit.md b/docs/sveltekit.md index e00ee170..abf40f23 100644 --- a/docs/sveltekit.md +++ b/docs/sveltekit.md @@ -75,3 +75,18 @@ The overall theme of the front end is defined by [DaisyUI](https://daisyui.com/d #### Opinionated use of Shadows The front end makes some use of colored shadows with the `shadow-primary` and `shadow-secondary` DaisyUI classes. Just use the search and replace function to change this to a more neutral look, if you find the color too much. + +#### Color Scheme Helper + +Some JS modules do not accept DaisyUI/TailwindCSS color class names. A small helper function can be imported and used to convert any CSS variable name for a DaisyUI color into HSLA. That way modules like e.g. Charts.js can be styled in the current color scheme in a responsive manner. + +```js +import { daisyColor } from "$lib/DaisyUiHelper"; + +borderColor: daisyColor('--p'), +backgroundColor: daisyColor('--p', 50), +``` + +## TS Types Definition + +All types used throughout the front end are exported from [models.ts](https://github.com/theelims/ESP32-sveltekit/blob/main/interface/src/lib/types/models.ts). It is a convenient location to add your custom types once you expand the front end. diff --git a/interface/package-lock.json b/interface/package-lock.json index 21889513..9a5599e7 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -11,7 +11,8 @@ "chart.js": "^4.4.0", "compare-versions": "^6.0.0", "daisyui": "^3.5.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", + "postcss-remove-classes": "^2.0.0", "svelte-dnd-list": "^0.1.8", "svelte-modals": "^1.3.0" }, @@ -36,7 +37,7 @@ "tailwindcss": "^3.3.3", "tslib": "^2.6.1", "typescript": "^5.1.6", - "unplugin-icons": "^0.17.0", + "unplugin-icons": "^0.18.5", "vite": "^4.4.8" } }, @@ -61,12 +62,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -86,9 +87,9 @@ } }, "node_modules/@antfu/utils": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.6.tgz", - "integrity": "sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", + "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" @@ -462,18 +463,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -493,38 +494,82 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@fastify/busboy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", - "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -539,15 +584,15 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@iconify-json/tabler": { - "version": "1.1.94", - "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.1.94.tgz", - "integrity": "sha512-rjNUm5q/RiKRPIrmZz7b7F864HkUTIfGXA20GbTCDgEUvF2mHyUiSzlXf5f45WzGhGoAeJdeGoWGWvnODyBj0w==", + "version": "1.1.110", + "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.1.110.tgz", + "integrity": "sha512-+0TbyNeQpFI2r+bjtbazGrpGskI3c9NTii/6HhWTS+/d5+PiLs6+wWJW0M9AU2ykew7zdMKB2WtyczFyjYzZIQ==", "dev": true, "dependencies": { "@iconify/types": "*" @@ -560,44 +605,86 @@ "dev": true }, "node_modules/@iconify/utils": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.11.tgz", - "integrity": "sha512-M/w3PkN8zQYXi8N6qK/KhnYMfEbbb6Sk8RZVn8g+Pmmu5ybw177RpsaGwpziyHeUsu4etrexYSWq3rwnIqzYCg==", + "version": "2.1.23", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.23.tgz", + "integrity": "sha512-YGNbHKM5tyDvdWZ92y2mIkrfvm5Fvhe6WJSkWu7vvOFhMtYDP0casZpoRz0XEHZCrYsR4stdGT3cZ52yp5qZdQ==", "dev": true, "dependencies": { "@antfu/install-pkg": "^0.1.1", - "@antfu/utils": "^0.7.5", + "@antfu/utils": "^0.7.7", "@iconify/types": "^2.0.0", "debug": "^4.3.4", "kolorist": "^1.8.0", - "local-pkg": "^0.4.3" + "local-pkg": "^0.5.0", + "mlly": "^1.6.1" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -608,9 +695,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -653,19 +740,28 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@polka/url": { - "version": "1.0.0-next.23", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", - "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, "node_modules/@sveltejs/adapter-auto": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.0.tgz", - "integrity": "sha512-o2pZCfATFtA/Gw/BB0Xm7k4EYaekXxaPGER3xGSY3FvzFJGTlJlZjBseaXwYSM94lZ0HniOjTokN3cWaLX6fow==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-2.1.1.tgz", + "integrity": "sha512-nzi6x/7/3Axh5VKQ8Eed3pYxastxoa06Y/bFhWb7h3Nu+nGRVxKAy3+hBJgmPCwWScy8n0TsstZjSVKfyrIHkg==", "dev": true, "dependencies": { - "import-meta-resolve": "^3.0.0" + "import-meta-resolve": "^4.0.0" }, "peerDependencies": { "@sveltejs/kit": "^1.0.0" @@ -681,9 +777,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "1.30.3", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.3.tgz", - "integrity": "sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==", + "version": "1.30.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.4.tgz", + "integrity": "sha512-JSQIQT6XvdchCRQEm7BABxPC56WP5RYVONAi+09S8tmzeP43fBsRlr95bFmsTQM2RHBldfgQk+jgdnsKI75daA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -699,7 +795,7 @@ "set-cookie-parser": "^2.6.0", "sirv": "^2.0.2", "tiny-glob": "^0.2.9", - "undici": "~5.26.2" + "undici": "^5.28.3" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -752,45 +848,45 @@ } }, "node_modules/@types/cookie": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", - "integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.4.tgz", + "integrity": "sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==", "dev": true }, "node_modules/@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/pug": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.7.tgz", - "integrity": "sha512-I469DU0UXNC1aHepwirWhu9YKg5fkxohZD95Ey/5A7lovC+Siu+MCLffva87lnfThaOrw9Vb1DUN5t55oULAAw==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", "dev": true }, "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -816,15 +912,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -844,13 +940,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -861,13 +957,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -888,9 +984,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -901,16 +997,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -928,17 +1025,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -953,12 +1050,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -969,10 +1066,16 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -1009,7 +1112,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1018,7 +1120,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1075,9 +1176,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -1094,9 +1195,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -1112,9 +1213,9 @@ } }, "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", "dependencies": { "dequal": "^2.0.3" } @@ -1125,20 +1226,22 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1153,9 +1256,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -1172,9 +1275,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -1211,9 +1314,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001546", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", - "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true, "funding": [ { @@ -1247,26 +1350,20 @@ } }, "node_modules/chart.js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", - "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", "dependencies": { "@kurkle/color": "^0.3.0" }, "engines": { - "pnpm": ">=7" + "pnpm": ">=8" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1279,6 +1376,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -1310,7 +1410,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1321,8 +1420,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/colord": { "version": "2.9.3", @@ -1345,7 +1443,14 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true }, "node_modules/cookie": { "version": "0.5.0", @@ -1360,7 +1465,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1403,9 +1507,9 @@ } }, "node_modules/daisyui": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.2.tgz", - "integrity": "sha512-yJZ1QjHUaL+r9BkquTdzNHb7KIgAJVFh0zbOXql2Wu0r7zx5qZNLxclhjN0WLoIpY+o2h/8lqXg7ijj8oTigOw==", + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.4.tgz", + "integrity": "sha512-fvi2RGH4YV617/6DntOVGcOugOPym9jTGWW2XySb5ZpvdWO4L7bEG77VHirrnbRUEWvIEVXkBpxUz2KFj0rVnA==", "dependencies": { "colord": "^2.9", "css-selector-tokenizer": "^0.8", @@ -1471,9 +1575,9 @@ } }, "node_modules/devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.3.tgz", + "integrity": "sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg==", "dev": true }, "node_modules/didyoumean": { @@ -1510,12 +1614,22 @@ "node": ">=6.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/electron-to-chromium": { - "version": "1.4.544", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.544.tgz", - "integrity": "sha512-54z7squS1FyFRSUqq/knOFSptjjogLZXbKcYk3B0qkE1KZzvqASwRZnY2KzZQJqIYLVD38XZeoiMRflYSwyO4w==", + "version": "1.4.745", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz", + "integrity": "sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==", "dev": true }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -1560,9 +1674,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -1581,18 +1695,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -1634,10 +1749,25 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -1647,22 +1777,23 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.34.0.tgz", - "integrity": "sha512-4RYUgNai7wr0v+T/kljMiYSjC/oqwgq5i+cPppawryAayj4C7WK1ixFlWCGmNmBppnoKCl4iA4ZPzPtlHcb4CA==", + "version": "2.37.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.37.0.tgz", + "integrity": "sha512-H/2Gz7agYHEMEEzRuLYuCmAIdjuBnbhFG9hOK0yCdSBvvJGJMkjo+lR6j67OIvLOavgp4L7zA5LnDKi8WqdPhQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "debug": "^4.3.4", + "eslint-compat-utils": "^0.5.0", "esutils": "^2.0.3", - "known-css-properties": "^0.28.0", - "postcss": "^8.4.5", + "known-css-properties": "^0.30.0", + "postcss": "^8.4.38", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" + "postcss-selector-parser": "^6.0.16", + "semver": "^7.6.0", + "svelte-eslint-parser": ">=0.34.0 <1.0.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" @@ -1671,8 +1802,8 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0", - "svelte": "^3.37.0 || ^4.0.0" + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.95" }, "peerDependenciesMeta": { "svelte": { @@ -1708,6 +1839,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/esm-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", @@ -1811,9 +1964,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1854,9 +2007,9 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -1901,9 +2054,9 @@ } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", @@ -1911,19 +2064,45 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fraction.js": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", - "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "engines": { "node": "*" @@ -1936,7 +2115,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -1951,6 +2131,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -1994,10 +2182,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2053,14 +2263,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2070,6 +2272,17 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2080,9 +2293,9 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -2105,9 +2318,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", - "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", "dev": true, "funding": { "type": "github", @@ -2127,6 +2340,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2135,7 +2349,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -2149,11 +2364,11 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2167,6 +2382,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2218,13 +2441,29 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } }, "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } @@ -2260,9 +2499,12 @@ "dev": true }, "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } }, "node_modules/keyv": { "version": "4.5.4", @@ -2283,9 +2525,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", - "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", + "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", "dev": true }, "node_modules/kolorist": { @@ -2321,10 +2563,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, "engines": { "node": ">=14" }, @@ -2352,6 +2598,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2371,14 +2622,11 @@ } }, "node_modules/magic-string": { - "version": "0.30.4", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", - "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/mdn-data": { @@ -2431,14 +2679,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -2450,6 +2701,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -2462,6 +2721,18 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mlly": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", + "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, "node_modules/mousetrap": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", @@ -2503,9 +2774,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -2526,9 +2797,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -2580,6 +2851,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -2671,6 +2943,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2679,7 +2952,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -2689,6 +2961,29 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -2698,6 +2993,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -2740,10 +3041,21 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz", + "integrity": "sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.6.1", + "pathe": "^1.1.2" + } + }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -2759,9 +3071,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2848,6 +3160,18 @@ "postcss": "^8.2.14" } }, + "node_modules/postcss-remove-classes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-remove-classes/-/postcss-remove-classes-2.0.0.tgz", + "integrity": "sha512-poz1yQ4vNSqfnXMd+NhlE8c9vRl1BAXOcb/1yA557G0kEKIpK8HhWaAYsEYmYEgpvF7In3ShZT9ohXh2wzKp4w==", + "dependencies": { + "lodash": "^4.17.21", + "postcss-selector-parser": "^6.0.6" + }, + "peerDependencies": { + "postcss": "^8.3.11" + } + }, "node_modules/postcss-safe-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", @@ -2891,9 +3215,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2917,9 +3241,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -2932,19 +3256,19 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.0.3.tgz", - "integrity": "sha512-dLhieh4obJEK1hnZ6koxF+tMUrZbV5YGvRpf2+OADyanjya5j0z1Llo8iGwiHmFWZVG/hLEw/AJD5chXd9r3XA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.3.tgz", + "integrity": "sha512-wJq8RunyFlWco6U0WJV5wNCM7zpBFakS76UBSbmzMGpncpK98NZABaE+s7n8/APDCEVNHXC5Mpq+MLebQtsRlg==", "dev": true, "peerDependencies": { "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0" + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.5.tgz", - "integrity": "sha512-voy0CjWv/CM8yeaduv5ZwovovpTGMR5LbzlhGF+LtEvMJt9wBeVTVnW781hL38R/RcDXCJwN2rolsgr94B/n0Q==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz", + "integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==", "dev": true, "engines": { "node": ">=14.21.3" @@ -2953,15 +3277,17 @@ "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", - "@shufo/prettier-plugin-blade": "*", "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, @@ -2975,10 +3301,10 @@ "@shopify/prettier-plugin-liquid": { "optional": true }, - "@shufo/prettier-plugin-blade": { + "@trivago/prettier-plugin-sort-imports": { "optional": true }, - "@trivago/prettier-plugin-sort-imports": { + "@zackad/prettier-plugin-twig-melody": { "optional": true }, "prettier-plugin-astro": { @@ -3002,21 +3328,21 @@ "prettier-plugin-organize-imports": { "optional": true }, - "prettier-plugin-style-order": { + "prettier-plugin-sort-imports": { "optional": true }, - "prettier-plugin-svelte": { + "prettier-plugin-style-order": { "optional": true }, - "prettier-plugin-twig-melody": { + "prettier-plugin-svelte": { "optional": true } } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -3061,9 +3387,9 @@ } }, "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -3184,9 +3510,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3208,7 +3534,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -3220,7 +3545,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -3232,19 +3556,28 @@ "dev": true }, "node_modules/sirv": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", - "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "dev": true, "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", "totalist": "^3.0.0" }, "engines": { "node": ">= 10" } }, + "node_modules/sirv/node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3270,18 +3603,89 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3323,13 +3727,13 @@ } }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -3340,23 +3744,25 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3386,22 +3792,23 @@ } }, "node_modules/svelte": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz", - "integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.15.tgz", + "integrity": "sha512-j9KJSccHgLeRERPlhMKrCXpk2TqL2m5Z+k+OBTQhZOhIdCCd3WfqV+ylPWeipEwq17P/ekiSFWwrVQv93i3bsg==", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", "acorn": "^8.9.0", "aria-query": "^5.3.0", - "axobject-query": "^3.2.1", + "axobject-query": "^4.0.0", "code-red": "^1.0.3", "css-tree": "^2.3.1", "estree-walker": "^3.0.3", "is-reference": "^3.0.1", "locate-character": "^3.0.0", - "magic-string": "^0.30.0", + "magic-string": "^0.30.4", "periscopic": "^3.1.0" }, "engines": { @@ -3409,9 +3816,9 @@ } }, "node_modules/svelte-check": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.5.2.tgz", - "integrity": "sha512-5a/YWbiH4c+AqAUP+0VneiV5bP8YOk9JL3jwvN+k2PEPLgpu85bjQc5eE67+eIZBBwUEJzmO3I92OqKcqbp3fw==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.9.tgz", + "integrity": "sha512-hDQrk3L0osX07djQyMiXocKysTLfusqi8AriNcCiQxhQR49/LonYolcUGMtZ0fbUR8HTR198Prrgf52WWU9wEg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -3420,14 +3827,14 @@ "import-fresh": "^3.2.1", "picocolors": "^1.0.0", "sade": "^1.7.4", - "svelte-preprocess": "^5.0.4", + "svelte-preprocess": "^5.1.3", "typescript": "^5.0.3" }, "bin": { "svelte-check": "bin/svelte-check" }, "peerDependencies": { - "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0" + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" } }, "node_modules/svelte-dnd-list": { @@ -3436,16 +3843,16 @@ "integrity": "sha512-81Nt/niux7kf59lql0lxTAH0z8xwAxHdHC9dZT7MbfE32T6hgeLsLZ7RIBNAihQ040Io1KghqqPXD+k2viiKeA==" }, "node_modules/svelte-eslint-parser": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz", - "integrity": "sha512-vo7xPGTlKBGdLH8T5L64FipvTrqv3OQRx9d2z5X05KKZDlF4rQk8KViZO4flKERY+5BiVdOh7zZ7JGJWo5P0uA==", + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.34.1.tgz", + "integrity": "sha512-9+uLA1pqI9AZioKVGJzYYmlOZWxfoCXSbAM9iaNm7H01XlYlzRTtJfZgl9o3StQGN41PfGJIbkKkfk3e/pHFfA==", "dev": true, "dependencies": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.29", - "postcss-scss": "^4.0.8" + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.38", + "postcss-scss": "^4.0.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3454,7 +3861,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.94" }, "peerDependenciesMeta": { "svelte": { @@ -3492,32 +3899,32 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz", - "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", + "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -3553,32 +3960,20 @@ } } }, - "node_modules/svelte-preprocess/node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -3602,20 +3997,26 @@ } }, "node_modules/tailwindcss/node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" }, "engines": { "node": ">= 14" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" @@ -3629,10 +4030,24 @@ } } }, + "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -3693,12 +4108,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -3740,9 +4155,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3752,10 +4167,16 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, "node_modules/undici": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz", - "integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -3765,30 +4186,33 @@ } }, "node_modules/unplugin": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.5.0.tgz", - "integrity": "sha512-9ZdRwbh/4gcm1JTOkp9lAkIDrtOyOxgHmY7cjuwI8L/2RTikMcVG25GsZwNAgRuap3iDw2jeq7eoqtAsz5rW3A==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.10.1.tgz", + "integrity": "sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==", "dev": true, "dependencies": { - "acorn": "^8.10.0", - "chokidar": "^3.5.3", + "acorn": "^8.11.3", + "chokidar": "^3.6.0", "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.5.0" + "webpack-virtual-modules": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/unplugin-icons": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.17.0.tgz", - "integrity": "sha512-gMv66eY/Hj64heM55XrfDH3LUCWI51mtkBVUPVl9VkpvLgAYhdVe9nRuzu6p+idmCLSQVq7xiPxQcD4aXCgW5A==", + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.18.5.tgz", + "integrity": "sha512-KVNAohXbZ7tVcG1C3p6QaC7wU9Qrj7etv4XvsMMJAxr5LccQZ+Iuv5LOIv/7GtqXaGN1BuFCqRO1ErsHEgEXdQ==", "dev": true, "dependencies": { - "@antfu/install-pkg": "^0.1.1", - "@antfu/utils": "^0.7.6", - "@iconify/utils": "^2.1.9", + "@antfu/install-pkg": "^0.3.1", + "@antfu/utils": "^0.7.7", + "@iconify/utils": "^2.1.22", "debug": "^4.3.4", "kolorist": "^1.8.0", - "local-pkg": "^0.4.3", - "unplugin": "^1.4.0" + "local-pkg": "^0.5.0", + "unplugin": "^1.6.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -3818,6 +4242,152 @@ } } }, + "node_modules/unplugin-icons/node_modules/@antfu/install-pkg": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.3.2.tgz", + "integrity": "sha512-FFYqME8+UHlPnRlX/vn+8cTD4Wo/nG/lzRxpABs3XANBmdJdNImVz3QvjNAE/W3PSCNbG387FOz8o5WelnWOlg==", + "dev": true, + "dependencies": { + "execa": "^8.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unplugin-icons/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/unplugin-icons/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/unplugin-icons/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unplugin-icons/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unplugin-icons/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -3863,9 +4433,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -3941,16 +4511,15 @@ } }, "node_modules/webpack-virtual-modules": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", - "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", + "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", "dev": true }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -3961,10 +4530,98 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/yallist": { "version": "4.0.0", diff --git a/interface/package.json b/interface/package.json index b1099e96..8cf9e180 100644 --- a/interface/package.json +++ b/interface/package.json @@ -32,7 +32,7 @@ "tailwindcss": "^3.3.3", "tslib": "^2.6.1", "typescript": "^5.1.6", - "unplugin-icons": "^0.17.0", + "unplugin-icons": "^0.18.5", "vite": "^4.4.8" }, "type": "module", @@ -40,7 +40,8 @@ "chart.js": "^4.4.0", "compare-versions": "^6.0.0", "daisyui": "^3.5.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", + "postcss-remove-classes": "^2.0.0", "svelte-dnd-list": "^0.1.8", "svelte-modals": "^1.3.0" } diff --git a/interface/postcss.config.cjs b/interface/postcss.config.cjs deleted file mode 100644 index 33ad091d..00000000 --- a/interface/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/interface/postcss.config.js b/interface/postcss.config.js new file mode 100644 index 00000000..0c738e05 --- /dev/null +++ b/interface/postcss.config.js @@ -0,0 +1,11 @@ +import tailwindcss from 'tailwindcss'; +import autoprefixer from 'autoprefixer'; +import postcssRemoveClasses from 'postcss-remove-classes'; + +export default { + plugins: [ + tailwindcss(), + autoprefixer() + //postcssRemoveClasses.default('range') + ] +}; diff --git a/interface/src/lib/stores/analytics.ts b/interface/src/lib/stores/analytics.ts index 3720083b..d8fdb17c 100644 --- a/interface/src/lib/stores/analytics.ts +++ b/interface/src/lib/stores/analytics.ts @@ -12,7 +12,7 @@ let analytics_data = { core_temp: [] }; -const maxAnalyticsData = 100; +const maxAnalyticsData = 1000; // roughly 33 Minutes of data at 1 update per 2 seconds function createAnalytics() { const { subscribe, update } = writable(analytics_data); @@ -41,4 +41,4 @@ function createAnalytics() { }; } -export const analytics = createAnalytics(); \ No newline at end of file +export const analytics = createAnalytics(); diff --git a/interface/src/lib/stores/socket.ts b/interface/src/lib/stores/socket.ts new file mode 100644 index 00000000..e230c25b --- /dev/null +++ b/interface/src/lib/stores/socket.ts @@ -0,0 +1,115 @@ +import { writable } from 'svelte/store'; + +function createWebSocket() { + let listeners = new Map void>>(); + const { subscribe, set } = writable(false); + const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const; + type SocketEvent = (typeof socketEvents)[number]; + let unresponsiveTimeoutId: number; + let reconnectTimeoutId: number; + let ws: WebSocket; + let socketUrl: string | URL; + + function init(url: string | URL) { + socketUrl = url; + connect(); + } + + function disconnect(reason: SocketEvent, event?: Event) { + ws.close(); + set(false); + clearTimeout(unresponsiveTimeoutId); + clearTimeout(reconnectTimeoutId); + listeners.get(reason)?.forEach((listener) => listener(event)); + reconnectTimeoutId = setTimeout(connect, 1000); + } + + function connect() { + ws = new WebSocket(socketUrl); + ws.onopen = (ev) => { + set(true); + clearTimeout(reconnectTimeoutId); + listeners.get('open')?.forEach((listener) => listener(ev)); + for (const event of listeners.keys()) { + if (socketEvents.includes(event as SocketEvent)) continue; + sendEvent('subscribe', event); + } + }; + ws.onmessage = (message) => { + resetUnresponsiveCheck(); + let data = message.data; + + if (data instanceof ArrayBuffer) { + listeners.get('binary')?.forEach((listener) => listener(data)); + return; + } + listeners.get('message')?.forEach((listener) => listener(data)); + try { + data = JSON.parse(message.data); + } catch (error) { + listeners.get('error')?.forEach((listener) => listener(error)); + return; + } + listeners.get('json')?.forEach((listener) => listener(data)); + const [event, payload] = data; + if (event) listeners.get(event)?.forEach((listener) => listener(payload)); + }; + ws.onerror = (ev) => disconnect('error', ev); + ws.onclose = (ev) => disconnect('close', ev); + } + + function unsubscribe(event: string, listener?: (data: any) => void) { + let eventListeners = listeners.get(event); + if (!eventListeners) return; + + if (!eventListeners.size) { + sendEvent('unsubscribe', event); + } + if (listener) { + eventListeners?.delete(listener); + } else { + listeners.delete(event); + } + } + + function resetUnresponsiveCheck() { + clearTimeout(unresponsiveTimeoutId); + unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), 2000); + } + + function send(msg: unknown) { + if (!ws || ws.readyState !== WebSocket.OPEN) return; + ws.send(JSON.stringify(msg)); + } + + function sendEvent(event: string, data: unknown) { + send({ event, data }); + } + + return { + subscribe, + send, + sendEvent, + init, + on: (event: string, listener: (data: T) => void): (() => void) => { + let eventListeners = listeners.get(event); + if (!eventListeners) { + if (!socketEvents.includes(event as SocketEvent)) { + sendEvent('subscribe', event); + } + eventListeners = new Set(); + listeners.set(event, eventListeners); + } + eventListeners.add(listener as (data: any) => void); + + return () => { + unsubscribe(event, listener); + }; + }, + off: (event: string, listener?: (data: any) => void) => { + unsubscribe(event, listener); + } + }; +} + +export const socket = createWebSocket(); diff --git a/interface/src/lib/stores/telemetry.ts b/interface/src/lib/stores/telemetry.ts index 5fefb0eb..c6eead61 100644 --- a/interface/src/lib/stores/telemetry.ts +++ b/interface/src/lib/stores/telemetry.ts @@ -1,7 +1,6 @@ import { writable } from 'svelte/store'; let telemetry_data = { - serverAvailable: true, rssi: { rssi: 0, disconnected: true diff --git a/interface/src/lib/stores/user.ts b/interface/src/lib/stores/user.ts index c8f3e21a..4569ae8a 100644 --- a/interface/src/lib/stores/user.ts +++ b/interface/src/lib/stores/user.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; import { goto } from '$app/navigation'; -import jwt_decode from 'jwt-decode'; +import { jwtDecode } from 'jwt-decode'; export type userProfile = { username: string; @@ -31,7 +31,7 @@ function createStore() { return { subscribe, init: (access_token: string) => { - const decoded: decodedJWT = jwt_decode(access_token); + const decoded: decodedJWT = jwtDecode(access_token); const userdata = { bearer_token: access_token, username: decoded.username, diff --git a/interface/src/routes/+layout.svelte b/interface/src/routes/+layout.svelte index bf5778c8..cd578c50 100644 --- a/interface/src/routes/+layout.svelte +++ b/interface/src/routes/+layout.svelte @@ -4,6 +4,7 @@ import { user } from '$lib/stores/user'; import { telemetry } from '$lib/stores/telemetry'; import { analytics } from '$lib/stores/analytics'; + import { socket } from '$lib/stores/socket'; import type { userProfile } from '$lib/stores/user'; import { page } from '$app/stores'; import { Modals, closeModal } from 'svelte-modals'; @@ -22,10 +23,42 @@ if ($user.bearer_token !== '') { await validateUser($user); } - connectToEventSource(); + const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : ''; + socket.init(`ws://${window.location.host}/ws/events${ws_token}`); + + addEventListeners(); + }); + + onDestroy(() => { + removeEventListeners(); }); - onDestroy(() => disconnectEventSource()); + const addEventListeners = () => { + socket.on('open', handleOpen); + socket.on('close', handleClose); + socket.on('error', handleError); + socket.on('rssi', handleNetworkStatus); + socket.on('infoToast', handleInfoToast); + socket.on('successToast', handleSuccessToast); + socket.on('warningToast', handleWarningToast); + socket.on('errorToast', handleErrorToast); + if ($page.data.features.analytics) socket.on('analytics', handleAnalytics); + if ($page.data.features.battery) socket.on('battery', handleBattery); + if ($page.data.features.download_firmware) socket.on('otastatus', handleOAT); + }; + + const removeEventListeners = () => { + socket.off('analytics', handleAnalytics); + socket.off('open', handleOpen); + socket.off('close', handleClose); + socket.off('rssi', handleNetworkStatus); + socket.off('infoToast', handleInfoToast); + socket.off('successToast', handleSuccessToast); + socket.off('warningToast', handleWarningToast); + socket.off('errorToast', handleErrorToast); + socket.off('battery', handleBattery); + socket.off('otastatus', handleOAT); + }; async function validateUser(userdata: userProfile) { try { @@ -44,73 +77,31 @@ } } - let menuOpen = false; + const handleOpen = () => { + notifications.success('Connection to device established', 5000); + }; - let eventSourceUrl = '/events'; - let eventSource: EventSource; - let unresponsiveTimeoutId: number; - - function connectToEventSource() { - eventSource = new EventSource(eventSourceUrl); - - eventSource.addEventListener('open', () => { - notifications.success('Connection to device established', 5000); - telemetry.setRSSI('found'); // Update store and flag as server being available again - }); - - eventSource.addEventListener('rssi', (event) => { - telemetry.setRSSI(event.data); - resetUnresponsiveCheck(); - }); - - eventSource.addEventListener('error', (event) => { - reconnectEventSource(); - }); - - eventSource.addEventListener('infoToast', (event) => { - notifications.info(event.data, 5000); - }); - - eventSource.addEventListener('successToast', (event) => { - notifications.success(event.data, 5000); - }); - - eventSource.addEventListener('warningToast', (event) => { - notifications.warning(event.data, 5000); - }); - - eventSource.addEventListener('errorToast', (event) => { - notifications.error(event.data, 5000); - }); - - eventSource.addEventListener('battery', (event) => { - telemetry.setBattery(event.data); - }); - - eventSource.addEventListener('download_ota', (event) => { - telemetry.setDownloadOTA(event.data); - }); - eventSource.addEventListener('analytics', (event) => { - const data = JSON.parse(event.data) as Analytics; - analytics.addData(data); - }); - } + const handleClose = () => { + notifications.error('Connection to device lost', 5000); + telemetry.setRSSI('lost'); + }; - function disconnectEventSource() { - clearTimeout(unresponsiveTimeoutId); - eventSource?.close(); - } + const handleError = (data: any) => console.error(data); - function reconnectEventSource() { - notifications.error('Connection to device lost', 5000); - disconnectEventSource(); - connectToEventSource(); - } + const handleInfoToast = (data: string) => notifications.info(data, 5000); + const handleWarningToast = (data: string) => notifications.warning(data, 5000); + const handleErrorToast = (data: string) => notifications.error(data, 5000); + const handleSuccessToast = (data: string) => notifications.success(data, 5000); - function resetUnresponsiveCheck() { - clearTimeout(unresponsiveTimeoutId); - unresponsiveTimeoutId = setTimeout(() => reconnectEventSource(), 2000); - } + const handleAnalytics = (data: Analytics) => analytics.addData(data); + + const handleNetworkStatus = (data: string) => telemetry.setRSSI(data); + + const handleBattery = (data: string) => telemetry.setBattery(data); + + const handleOAT = (data: string) => telemetry.setDownloadOTA(data); + + let menuOpen = false; diff --git a/interface/src/routes/demo/Demo.svelte b/interface/src/routes/demo/Demo.svelte index e1ba44db..fbae3be0 100644 --- a/interface/src/routes/demo/Demo.svelte +++ b/interface/src/routes/demo/Demo.svelte @@ -8,6 +8,7 @@ import Info from '~icons/tabler/info-circle'; import Save from '~icons/tabler/device-floppy'; import Reload from '~icons/tabler/reload'; + import { socket } from '$lib/stores/socket'; import type { LightState } from '$lib/types/models'; let lightState: LightState = { led_on: false }; @@ -31,35 +32,15 @@ return; } - const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : ''; - - const lightStateSocket = new WebSocket('ws://' + $page.url.host + '/ws/lightState' + ws_token); - - lightStateSocket.onopen = (event) => { - lightStateSocket.send('Hello'); - }; - - lightStateSocket.addEventListener('close', (event) => { - const closeCode = event.code; - const closeReason = event.reason; - console.log('WebSocket closed with code:', closeCode); - console.log('Close reason:', closeReason); - notifications.error('Websocket disconnected', 5000); - }); - - lightStateSocket.onmessage = (event) => { - const message = JSON.parse(event.data); - if (message.type != 'id') { - lightState = message; - } - }; - - onDestroy(() => lightStateSocket.close()); - onMount(() => { + socket.on('led', (data) => { + lightState = data; + }); getLightstate(); }); + onDestroy(() => socket.off('led')); + async function postLightstate() { try { const response = await fetch('/rest/lightState', { @@ -109,12 +90,12 @@ >
-

Websocket Example

+

Event Socket Example

The switch below controls the LED via the WebSocket. It will automatically update whenever - the LED state changes.The switch below controls the LED via the event system which uses WebSocket under the hood. + It will automatically update whenever the LED state changes.
@@ -125,7 +106,7 @@ class="toggle toggle-primary" bind:checked={lightState.led_on} on:change={() => { - lightStateSocket.send(JSON.stringify(lightState)); + socket.sendEvent('led', lightState); }} /> diff --git a/interface/src/routes/system/status/SystemStatus.svelte b/interface/src/routes/system/status/SystemStatus.svelte index 39ea5315..abe4b92c 100644 --- a/interface/src/routes/system/status/SystemStatus.svelte +++ b/interface/src/routes/system/status/SystemStatus.svelte @@ -24,7 +24,8 @@ import Health from '~icons/tabler/stethoscope'; import Stopwatch from '~icons/tabler/24-hours'; import SDK from '~icons/tabler/sdk'; - import type { SystemInformation } from '$lib/types/models'; + import type { SystemInformation, Analytics } from '$lib/types/models'; + import { socket } from '$lib/stores/socket'; let systemInformation: SystemInformation; @@ -44,13 +45,12 @@ return systemInformation; } - const interval = setInterval(async () => { - getSystemStatus(); - }, 5000); + onMount(() => socket.on('analytics', handleSystemData)); - onMount(() => getSystemStatus()); + onDestroy(() => socket.off('analytics', handleSystemData)); - onDestroy(() => clearInterval(interval)); + const handleSystemData = (data: Analytics) => + (systemInformation = { ...systemInformation, ...data }); async function postRestart() { const response = await fetch('/rest/restart', { @@ -250,7 +250,10 @@
Sketch (Used / Free)
- {((systemInformation.sketch_size / systemInformation.free_sketch_space) * 100).toFixed(1)} % of + {( + (systemInformation.sketch_size / systemInformation.free_sketch_space) * + 100 + ).toFixed(1)} % of {(systemInformation.free_sketch_space / 1000000).toLocaleString('en-US')} MB used @@ -292,9 +295,10 @@ > ({((systemInformation.fs_total - systemInformation.fs_used) / 1000000).toLocaleString( - 'en-US' - )} + >({( + (systemInformation.fs_total - systemInformation.fs_used) / + 1000000 + ).toLocaleString('en-US')} MB free)
@@ -308,7 +312,9 @@
Core Temperature
- {systemInformation.core_temp == 53.33 ? 'NaN' : systemInformation.core_temp.toFixed(2) + ' °C'} + {systemInformation.core_temp == 53.33 + ? 'NaN' + : systemInformation.core_temp.toFixed(2) + ' °C'}
diff --git a/interface/vite.config.ts b/interface/vite.config.ts index d82d8e65..e3072b52 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -19,11 +19,6 @@ const config: UserConfig = { target: 'http://192.168.1.89', changeOrigin: true }, - // Proxying REST: http://localhost:5173/rest/bar -> http://192.168.1.83/rest/bar - '/events': { - target: 'http://192.168.1.89', - changeOrigin: true - }, // Proxying websockets ws://localhost:5173/ws -> ws://192.168.1.83/ws '/ws': { target: 'ws://192.168.1.89', diff --git a/lib/PsychicHttp/src/PsychicClient.cpp b/lib/PsychicHttp/src/PsychicClient.cpp index 1e59d70e..1ef7067e 100644 --- a/lib/PsychicHttp/src/PsychicClient.cpp +++ b/lib/PsychicHttp/src/PsychicClient.cpp @@ -2,21 +2,24 @@ #include "PsychicHttpServer.h" #include -PsychicClient::PsychicClient(httpd_handle_t server, int socket) : - _server(server), - _socket(socket), - _friend(NULL), - isNew(false) -{} - -PsychicClient::~PsychicClient() { +PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server), + _socket(socket), + _friend(NULL), + isNew(false) +{ +} + +PsychicClient::~PsychicClient() +{ } -httpd_handle_t PsychicClient::server() { +httpd_handle_t PsychicClient::server() +{ return _server; } -int PsychicClient::socket() { +int PsychicClient::socket() +{ return _socket; } @@ -24,20 +27,21 @@ int PsychicClient::socket() { esp_err_t PsychicClient::close() { esp_err_t err = httpd_sess_trigger_close(_server, _socket); - //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. + // PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. return err; } IPAddress PsychicClient::localIP() { - IPAddress address(0,0,0,0); + IPAddress address(0, 0, 0, 0); char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing socklen_t addr_size = sizeof(addr); - if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) + { ESP_LOGE(PH_TAG, "Error getting client IP"); return address; } @@ -52,20 +56,21 @@ IPAddress PsychicClient::localIP() IPAddress PsychicClient::remoteIP() { - IPAddress address(0,0,0,0); + IPAddress address(0, 0, 0, 0); char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing socklen_t addr_size = sizeof(addr); - if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) + { ESP_LOGE(PH_TAG, "Error getting client IP"); return address; } // Convert to IPv4 string inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); - ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr); + ESP_LOGV(PH_TAG, "Client Remote IP => %s", ipstr); address.fromString(ipstr); return address; diff --git a/lib/PsychicHttp/src/PsychicWebSocket.cpp b/lib/PsychicHttp/src/PsychicWebSocket.cpp index 08b8418f..ddfe00d3 100644 --- a/lib/PsychicHttp/src/PsychicWebSocket.cpp +++ b/lib/PsychicHttp/src/PsychicWebSocket.cpp @@ -179,7 +179,7 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) } // okay, now try to load the packet - ESP_LOGI(PH_TAG, "frame len is %d", ws_pkt.len); + ESP_LOGV(PH_TAG, "frame len is %d", ws_pkt.len); if (ws_pkt.len) { /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ @@ -198,7 +198,7 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) free(buf); return ret; } - ESP_LOGI(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + ESP_LOGV(PH_TAG, "Got packet with message: %s", ws_pkt.payload); } // Text messages are our payload. diff --git a/lib/framework/APSettingsService.cpp b/lib/framework/APSettingsService.cpp index d59b4a8f..9f6b0e55 100644 --- a/lib/framework/APSettingsService.cpp +++ b/lib/framework/APSettingsService.cpp @@ -14,13 +14,15 @@ #include -APSettingsService::APSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager), - _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager), - _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE), - _dnsServer(nullptr), - _lastManaged(0), - _reconfigureAp(false) +APSettingsService::APSettingsService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager), + _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager), + _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE), + _dnsServer(nullptr), + _lastManaged(0), + _reconfigureAp(false) { addUpdateHandler([&](const String &originId) { reconfigureAP(); }, diff --git a/lib/framework/APStatus.cpp b/lib/framework/APStatus.cpp index 9b331e68..d6d65641 100644 --- a/lib/framework/APStatus.cpp +++ b/lib/framework/APStatus.cpp @@ -14,9 +14,11 @@ #include -APStatus::APStatus(PsychicHttpServer *server, SecurityManager *securityManager, APSettingsService *apSettingsService) : _server(server), - _securityManager(securityManager), - _apSettingsService(apSettingsService) +APStatus::APStatus(PsychicHttpServer *server, + SecurityManager *securityManager, + APSettingsService *apSettingsService) : _server(server), + _securityManager(securityManager), + _apSettingsService(apSettingsService) { } void APStatus::begin() diff --git a/lib/framework/AnalyticsService.h b/lib/framework/AnalyticsService.h index 915dcf26..3aaa15e3 100644 --- a/lib/framework/AnalyticsService.h +++ b/lib/framework/AnalyticsService.h @@ -16,22 +16,25 @@ #include #include #include -#include +#include #define MAX_ESP_ANALYTICS_SIZE 1024 +#define EVENT_ANALYTICS "analytics" #define ANALYTICS_INTERVAL 2000 class AnalyticsService { public: - AnalyticsService(NotificationEvents *notificationEvents) : _notificationEvents(notificationEvents){}; + AnalyticsService(EventSocket *socket) : _socket(socket){}; void begin() { + _socket->registerEvent(EVENT_ANALYTICS); + xTaskCreatePinnedToCore( this->_loopImpl, // Function that should be called "Analytics Service", // Name of the task (for debugging) - 4096, // Stack size (bytes) + 5120, // Stack size (bytes) this, // Pass reference to this class instance (tskIDLE_PRIORITY), // task priority NULL, // Task handle @@ -40,17 +43,17 @@ class AnalyticsService }; protected: - NotificationEvents *_notificationEvents; + EventSocket *_socket; static void _loopImpl(void *_this) { static_cast(_this)->_loop(); } void _loop() { - TickType_t xLastWakeTime; - xLastWakeTime = xTaskGetTickCount(); + TickType_t xLastWakeTime = xTaskGetTickCount(); + StaticJsonDocument doc; + char message[MAX_ESP_ANALYTICS_SIZE]; while (1) { - StaticJsonDocument doc; - String message; + doc.clear(); doc["uptime"] = millis() / 1000; doc["free_heap"] = ESP.getFreeHeap(); doc["total_heap"] = ESP.getHeapSize(); @@ -61,7 +64,7 @@ class AnalyticsService doc["core_temp"] = temperatureRead(); serializeJson(doc, message); - _notificationEvents->send(message, "analytics", millis()); + _socket->emit(EVENT_ANALYTICS, message); vTaskDelayUntil(&xLastWakeTime, ANALYTICS_INTERVAL / portTICK_PERIOD_MS); } diff --git a/lib/framework/BatteryService.cpp b/lib/framework/BatteryService.cpp index d4003cac..808bdc51 100644 --- a/lib/framework/BatteryService.cpp +++ b/lib/framework/BatteryService.cpp @@ -13,16 +13,21 @@ #include -BatteryService::BatteryService(NotificationEvents *notificationEvents) : _notificationEvents(notificationEvents) +BatteryService::BatteryService(EventSocket *socket) : _socket(socket) { } +void BatteryService::begin() +{ + _socket->registerEvent(EVENT_BATTERY); +} + void BatteryService::batteryEvent() { StaticJsonDocument<32> doc; - String message; + char message[32]; doc["soc"] = _lastSOC; doc["charging"] = _isCharging; serializeJson(doc, message); - _notificationEvents->send(message, "battery", millis()); + _socket->emit(EVENT_BATTERY, message); } diff --git a/lib/framework/BatteryService.h b/lib/framework/BatteryService.h index ff021a6c..2496a2a1 100644 --- a/lib/framework/BatteryService.h +++ b/lib/framework/BatteryService.h @@ -13,13 +13,17 @@ * the terms of the LGPL v3 license. See the LICENSE file for details. **/ +#include #include -#include + +#define EVENT_BATTERY "battery" class BatteryService { public: - BatteryService(NotificationEvents *notificationEvents); + BatteryService(EventSocket *socket); + + void begin(); void updateSOC(float stateOfCharge) { @@ -35,7 +39,7 @@ class BatteryService private: void batteryEvent(); - NotificationEvents *_notificationEvents; + EventSocket *_socket; int _lastSOC = 100; boolean _isCharging = false; }; diff --git a/lib/framework/DownloadFirmwareService.cpp b/lib/framework/DownloadFirmwareService.cpp index 110522a7..07a09ede 100644 --- a/lib/framework/DownloadFirmwareService.cpp +++ b/lib/framework/DownloadFirmwareService.cpp @@ -15,7 +15,7 @@ extern const uint8_t rootca_crt_bundle_start[] asm("_binary_src_certs_x509_crt_bundle_bin_start"); -static NotificationEvents *_notificationEvents = nullptr; +static EventSocket *_socket = nullptr; static int previousProgress = 0; StaticJsonDocument<128> doc; @@ -24,7 +24,7 @@ void update_started() String output; doc["status"] = "preparing"; serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); } void update_progress(int currentBytes, int totalBytes) @@ -35,8 +35,7 @@ void update_progress(int currentBytes, int totalBytes) if (progress > previousProgress) { doc["progress"] = progress; - serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); ESP_LOGV("Download OTA", "HTTP update process at %d of %d bytes... (%d %%)", currentBytes, totalBytes, progress); } previousProgress = progress; @@ -47,7 +46,7 @@ void update_finished() String output; doc["status"] = "finished"; serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); // delay to allow the event to be sent out vTaskDelay(100 / portTICK_PERIOD_MS); @@ -64,9 +63,9 @@ void updateTask(void *param) String url = *((String *)param); String output; - httpUpdate.onStart(update_started); - httpUpdate.onProgress(update_progress); - httpUpdate.onEnd(update_finished); + // httpUpdate.onStart(update_started); + // httpUpdate.onProgress(update_progress); + // httpUpdate.onEnd(update_finished); t_httpUpdate_return ret = httpUpdate.update(client, url.c_str()); @@ -77,7 +76,7 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = httpUpdate.getLastErrorString().c_str(); serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); ESP_LOGE("Download OTA", "HTTP Update failed with error (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); #ifdef SERIAL_INFO @@ -89,7 +88,7 @@ void updateTask(void *param) doc["status"] = "error"; doc["error"] = "Update failed, has same firmware version"; serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); ESP_LOGE("Download OTA", "HTTP Update failed, has same firmware version"); #ifdef SERIAL_INFO @@ -106,15 +105,18 @@ void updateTask(void *param) vTaskDelete(NULL); } -DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, NotificationEvents *notificationEvents) : _server(server), - _securityManager(securityManager), - _notificationEvents(notificationEvents) - +DownloadFirmwareService::DownloadFirmwareService(PsychicHttpServer *server, + SecurityManager *securityManager, + EventSocket *socket) : _server(server), + _securityManager(securityManager), + _socket(socket) { } void DownloadFirmwareService::begin() { + _socket->registerEvent(EVENT_DOWNLOAD_OTA); + _server->on(GITHUB_FIRMWARE_PATH, HTTP_POST, _securityManager->wrapCallback( @@ -143,7 +145,8 @@ esp_err_t DownloadFirmwareService::downloadUpdate(PsychicRequest *request, JsonV String output; serializeJson(doc, output); - _notificationEvents->send(output, "download_ota", millis()); + + _socket->emit(EVENT_DOWNLOAD_OTA, output.c_str()); if (xTaskCreatePinnedToCore( &updateTask, // Function that should be called diff --git a/lib/framework/DownloadFirmwareService.h b/lib/framework/DownloadFirmwareService.h index 652528a2..e9b6869c 100644 --- a/lib/framework/DownloadFirmwareService.h +++ b/lib/framework/DownloadFirmwareService.h @@ -18,27 +18,28 @@ #include #include +#include #include #include -#include #include #include // #include #define GITHUB_FIRMWARE_PATH "/rest/downloadUpdate" +#define EVENT_DOWNLOAD_OTA "otastatus" #define OTA_TASK_STACK_SIZE 9216 class DownloadFirmwareService { public: - DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, NotificationEvents *notificationEvents); + DownloadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager, EventSocket *socket); void begin(); private: SecurityManager *_securityManager; PsychicHttpServer *_server; - NotificationEvents *_notificationEvents; + EventSocket *_socket; esp_err_t downloadUpdate(PsychicRequest *request, JsonVariant &json); }; diff --git a/lib/framework/ESP32SvelteKit.cpp b/lib/framework/ESP32SvelteKit.cpp index 25783611..7c780a8c 100644 --- a/lib/framework/ESP32SvelteKit.cpp +++ b/lib/framework/ESP32SvelteKit.cpp @@ -6,7 +6,7 @@ * https://github.com/theelims/ESP32-sveltekit * * Copyright (C) 2018 - 2023 rjwats - * Copyright (C) 2023 theelims + * Copyright (C) 2024 theelims * * All Rights Reserved. This software may be modified and distributed under * the terms of the LGPL v3 license. See the LICENSE file for details. @@ -18,12 +18,12 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _numberEndpoints(numberEndpoints), _featureService(server), _securitySettingsService(server, &ESPFS), - _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_notificationEvents), + _wifiSettingsService(server, &ESPFS, &_securitySettingsService, &_socket), _wifiScanner(server, &_securitySettingsService), _wifiStatus(server, &_securitySettingsService), _apSettingsService(server, &ESPFS, &_securitySettingsService), _apStatus(server, &_securitySettingsService, &_apSettingsService), - _notificationEvents(server), + _socket(server, &_securitySettingsService, AuthenticationPredicates::IS_AUTHENTICATED), #if FT_ENABLED(FT_NTP) _ntpSettingsService(server, &ESPFS, &_securitySettingsService), _ntpStatus(server, &_securitySettingsService), @@ -32,7 +32,7 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _uploadFirmwareService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_DOWNLOAD_FIRMWARE) - _downloadFirmwareService(server, &_securitySettingsService, &_notificationEvents), + _downloadFirmwareService(server, &_securitySettingsService, &_socket), #endif #if FT_ENABLED(FT_MQTT) _mqttSettingsService(server, &ESPFS, &_securitySettingsService), @@ -45,10 +45,10 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _sleepService(server, &_securitySettingsService), #endif #if FT_ENABLED(FT_BATTERY) - _batteryService(&_notificationEvents), + _batteryService(&_socket), #endif #if FT_ENABLED(FT_ANALYTICS) - _analyticsService(&_notificationEvents), + _analyticsService(&_socket), #endif _restartService(server, &_securitySettingsService), _factoryResetService(server, &ESPFS, &_securitySettingsService), @@ -139,7 +139,7 @@ void ESP32SvelteKit::begin() // Start the services _apStatus.begin(); - _notificationEvents.begin(); + _socket.begin(); _apSettingsService.begin(); _factoryResetService.begin(); _featureService.begin(); @@ -173,6 +173,9 @@ void ESP32SvelteKit::begin() #if FT_ENABLED(FT_SLEEP) _sleepService.begin(); #endif +#if FT_ENABLED(FT_BATTERY) + _batteryService.begin(); +#endif // Start the loop task ESP_LOGV("ESP32SvelteKit", "Starting loop task"); diff --git a/lib/framework/ESP32SvelteKit.h b/lib/framework/ESP32SvelteKit.h index 339d30b7..6f41932f 100644 --- a/lib/framework/ESP32SvelteKit.h +++ b/lib/framework/ESP32SvelteKit.h @@ -27,9 +27,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -85,9 +85,9 @@ class ESP32SvelteKit return &_securitySettingsService; } - NotificationEvents *getNotificationEvents() + EventSocket *getSocket() { - return &_notificationEvents; + return &_socket; } #if FT_ENABLED(FT_SECURITY) @@ -170,7 +170,7 @@ class ESP32SvelteKit WiFiStatus _wifiStatus; APSettingsService _apSettingsService; APStatus _apStatus; - NotificationEvents _notificationEvents; + EventSocket _socket; #if FT_ENABLED(FT_NTP) NTPSettingsService _ntpSettingsService; NTPStatus _ntpStatus; diff --git a/lib/framework/EventEndpoint.h b/lib/framework/EventEndpoint.h new file mode 100644 index 00000000..c5041675 --- /dev/null +++ b/lib/framework/EventEndpoint.h @@ -0,0 +1,75 @@ +#ifndef EventEndpoint_h +#define EventEndpoint_h + +/** + * ESP32 SvelteKit + * + * A simple, secure and extensible framework for IoT projects for ESP32 platforms + * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. + * https://github.com/theelims/ESP32-sveltekit + * + * Copyright (C) 2018 - 2023 rjwats + * Copyright (C) 2023 theelims + * + * All Rights Reserved. This software may be modified and distributed under + * the terms of the LGPL v3 license. See the LICENSE file for details. + **/ + +#include +#include +#include +#include + +template +class EventEndpoint +{ +public: + EventEndpoint(JsonStateReader stateReader, + JsonStateUpdater stateUpdater, + StatefulService *statefulService, + EventSocket *socket, const char *event, + size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), + _stateUpdater(stateUpdater), + _statefulService(statefulService), + _socket(socket), + _bufferSize(bufferSize), + _event(event) + { + _statefulService->addUpdateHandler([&](const String &originId) + { syncState(originId); }, + false); + } + + void begin() + { + _socket->registerEvent(_event); + _socket->onEvent(_event, std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2)); + _socket->onSubscribe(_event, std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2)); + } + +private: + JsonStateReader _stateReader; + JsonStateUpdater _stateUpdater; + StatefulService *_statefulService; + EventSocket *_socket; + const char *_event; + size_t _bufferSize; + + void updateState(JsonObject &root, int originId) + { + _statefulService->update(root, _stateUpdater, String(originId)); + } + + void syncState(const String &originId, bool sync = false) + { + DynamicJsonDocument jsonDocument{_bufferSize}; + JsonObject root = jsonDocument.to(); + String output; + _statefulService->read(root, _stateReader); + serializeJson(root, output); + ESP_LOGV("EventEndpoint", "Syncing state: %s", output.c_str()); + _socket->emit(_event, output.c_str(), originId.c_str(), sync); + } +}; + +#endif diff --git a/lib/framework/EventSocket.cpp b/lib/framework/EventSocket.cpp new file mode 100644 index 00000000..17c49400 --- /dev/null +++ b/lib/framework/EventSocket.cpp @@ -0,0 +1,226 @@ +#include + +SemaphoreHandle_t clientSubscriptionsMutex = xSemaphoreCreateMutex(); + +EventSocket::EventSocket(PsychicHttpServer *server, + SecurityManager *securityManager, + AuthenticationPredicate authenticationPredicate) : _server(server), + _securityManager(securityManager), + _authenticationPredicate(authenticationPredicate), + _bufferSize(1024) +{ +} + +void EventSocket::begin() +{ + _socket.setFilter(_securityManager->filterRequest(_authenticationPredicate)); + _socket.onOpen((std::bind(&EventSocket::onWSOpen, this, std::placeholders::_1))); + _socket.onClose(std::bind(&EventSocket::onWSClose, this, std::placeholders::_1)); + _socket.onFrame(std::bind(&EventSocket::onFrame, this, std::placeholders::_1, std::placeholders::_2)); + _server->on(EVENT_SERVICE_PATH, &_socket); + + registerEvent("errorToast"); + registerEvent("warningToast"); + registerEvent("infoToast"); + registerEvent("successToast"); + + ESP_LOGV("EventSocket", "Registered event socket endpoint: %s", EVENT_SERVICE_PATH); +} + +void EventSocket::registerEvent(String event) +{ + if (!isEventValid(event)) + { + ESP_LOGV("EventSocket", "Registering event: %s", event.c_str()); + events.push_back(event); + } + else + { + ESP_LOGW("EventSocket", "Event already registered: %s", event.c_str()); + } +} + +void EventSocket::onWSOpen(PsychicWebSocketClient *client) +{ + ESP_LOGI("EventSocket", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket()); +} + +void EventSocket::onWSClose(PsychicWebSocketClient *client) +{ + for (auto &event_subscriptions : client_subscriptions) + { + event_subscriptions.second.remove(client->socket()); + } + ESP_LOGI("EventSocket", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket()); +} + +esp_err_t EventSocket::onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) +{ + ESP_LOGV("EventSocket", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(), + request->client()->socket(), frame->type); + + if (frame->type == HTTPD_WS_TYPE_TEXT) + { + ESP_LOGV("EventSocket", "ws[%s][%u] request: %s", request->client()->remoteIP().toString().c_str(), + request->client()->socket(), (char *)frame->payload); + + DynamicJsonDocument doc = DynamicJsonDocument(_bufferSize); + DeserializationError error = deserializeJson(doc, (char *)frame->payload, frame->len); + + if (!error && doc.is()) + { + String event = doc["event"]; + if (event == "subscribe") + { + // only subscribe to events that are registered + if (isEventValid(doc["data"].as())) + { + client_subscriptions[doc["data"]].push_back(request->client()->socket()); + handleSubscribeCallbacks(doc["data"], String(request->client()->socket())); + } + else + { + ESP_LOGW("EventSocket", "Client tried to subscribe to unregistered event: %s", doc["data"].as().c_str()); + } + } + else if (event == "unsubscribe") + { + client_subscriptions[doc["data"]].remove(request->client()->socket()); + } + else + { + JsonObject jsonObject = doc["data"].as(); + handleEventCallbacks(event, jsonObject, request->client()->socket()); + } + return ESP_OK; + } + } + return ESP_OK; +} + +void EventSocket::emit(String event, String payload) +{ + emit(event.c_str(), payload.c_str(), ""); +} + +void EventSocket::emit(const char *event, const char *payload) +{ + emit(event, payload, ""); +} + +void EventSocket::emit(const char *event, const char *payload, const char *originId, bool onlyToSameOrigin) +{ + // Only process valid events + if (!isEventValid(String(event))) + { + ESP_LOGW("EventSocket", "Method tried to emit unregistered event: %s", event); + return; + } + + int originSubscriptionId = originId[0] ? atoi(originId) : -1; + xSemaphoreTake(clientSubscriptionsMutex, portMAX_DELAY); + auto &subscriptions = client_subscriptions[event]; + if (subscriptions.empty()) + { + xSemaphoreGive(clientSubscriptionsMutex); + return; + } + String msg = "[\"" + String(event) + "\"," + String(payload) + "]"; + + // if onlyToSameOrigin == true, send the message back to the origin + if (onlyToSameOrigin && originSubscriptionId > 0) + { + auto *client = _socket.getClient(originSubscriptionId); + if (client) + { + ESP_LOGV("EventSocket", "Emitting event: %s to %s, Message: %s", event, client->remoteIP().toString().c_str(), + msg.c_str()); + client->sendMessage(msg.c_str()); + } + } + else + { // else send the message to all other clients + + for (int subscription : client_subscriptions[event]) + { + if (subscription == originSubscriptionId) + continue; + auto *client = _socket.getClient(subscription); + if (!client) + { + subscriptions.remove(subscription); + continue; + } + ESP_LOGV("EventSocket", "Emitting event: %s to %s, Message: %s", event, client->remoteIP().toString().c_str(), + msg.c_str()); + client->sendMessage(msg.c_str()); + } + } + xSemaphoreGive(clientSubscriptionsMutex); +} + +void EventSocket::pushNotification(String message, pushEvent event) +{ + String eventType; + switch (event) + { + case (PUSHERROR): + eventType = "errorToast"; + break; + case (PUSHWARNING): + eventType = "warningToast"; + break; + case (PUSHINFO): + eventType = "infoToast"; + break; + case (PUSHSUCCESS): + eventType = "successToast"; + break; + default: + ESP_LOGW("EventSocket", "Client tried invalid push notification: %s", event); + return; + } + emit(eventType.c_str(), message.c_str()); +} + +void EventSocket::handleEventCallbacks(String event, JsonObject &jsonObject, int originId) +{ + for (auto &callback : event_callbacks[event]) + { + callback(jsonObject, originId); + } +} + +void EventSocket::handleSubscribeCallbacks(String event, const String &originId) +{ + for (auto &callback : subscribe_callbacks[event]) + { + callback(originId, true); + } +} + +void EventSocket::onEvent(String event, EventCallback callback) +{ + if (!isEventValid(event)) + { + ESP_LOGW("EventSocket", "Method tried to register unregistered event: %s", event.c_str()); + return; + } + event_callbacks[event].push_back(callback); +} + +void EventSocket::onSubscribe(String event, SubscribeCallback callback) +{ + if (!isEventValid(event)) + { + ESP_LOGW("EventSocket", "Method tried to subscribe to unregistered event: %s", event.c_str()); + return; + } + subscribe_callbacks[event].push_back(callback); + ESP_LOGI("EventSocket", "onSubscribe for event: %s", event.c_str()); +} + +bool EventSocket::isEventValid(String event) +{ + return std::find(events.begin(), events.end(), event) != events.end(); +} diff --git a/lib/framework/EventSocket.h b/lib/framework/EventSocket.h new file mode 100644 index 00000000..84b52c2b --- /dev/null +++ b/lib/framework/EventSocket.h @@ -0,0 +1,67 @@ +#ifndef Socket_h +#define Socket_h + +#include +#include +#include +#include +#include +#include + +#define EVENT_SERVICE_PATH "/ws/events" + +typedef std::function EventCallback; +typedef std::function SubscribeCallback; + +enum pushEvent +{ + PUSHERROR, + PUSHWARNING, + PUSHINFO, + PUSHSUCCESS +}; + +class EventSocket +{ +public: + EventSocket(PsychicHttpServer *server, SecurityManager *_securityManager, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_AUTHENTICATED); + + void begin(); + + void registerEvent(String event); + + void onEvent(String event, EventCallback callback); + + void onSubscribe(String event, SubscribeCallback callback); + + void emit(String event, String payload); + + void emit(const char *event, const char *payload); + + void emit(const char *event, const char *payload, const char *originId, bool onlyToSameOrigin = false); + // if onlyToSameOrigin == true, the message will be sent to the originId only, otherwise it will be broadcasted to all clients except the originId + + void pushNotification(String message, pushEvent event); + +private: + PsychicHttpServer *_server; + PsychicWebSocketHandler _socket; + SecurityManager *_securityManager; + AuthenticationPredicate _authenticationPredicate; + + std::vector events; + std::map> client_subscriptions; + std::map> event_callbacks; + std::map> subscribe_callbacks; + void handleEventCallbacks(String event, JsonObject &jsonObject, int originId); + void handleSubscribeCallbacks(String event, const String &originId); + + bool isEventValid(String event); + + size_t _bufferSize; + void onWSOpen(PsychicWebSocketClient *client); + void onWSClose(PsychicWebSocketClient *client); + esp_err_t onFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame); +}; + +#endif diff --git a/lib/framework/FactoryResetService.cpp b/lib/framework/FactoryResetService.cpp index f84d9137..e7bb6448 100644 --- a/lib/framework/FactoryResetService.cpp +++ b/lib/framework/FactoryResetService.cpp @@ -16,9 +16,11 @@ using namespace std::placeholders; -FactoryResetService::FactoryResetService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager) : _server(server), - fs(fs), - _securityManager(securityManager) +FactoryResetService::FactoryResetService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager) : _server(server), + fs(fs), + _securityManager(securityManager) { } diff --git a/lib/framework/MqttPubSub.h b/lib/framework/MqttEndpoint.h similarity index 75% rename from lib/framework/MqttPubSub.h rename to lib/framework/MqttEndpoint.h index c90e8558..b97087e7 100644 --- a/lib/framework/MqttPubSub.h +++ b/lib/framework/MqttEndpoint.h @@ -1,5 +1,5 @@ -#ifndef MqttPubSub_h -#define MqttPubSub_h +#ifndef MqttEndpoint_h +#define MqttEndpoint_h /** * ESP32 SvelteKit @@ -21,33 +21,33 @@ #define MQTT_ORIGIN_ID "mqtt" template -class MqttPubSub +class MqttEndpoint { public: - MqttPubSub(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService *statefulService, - PsychicMqttClient *mqttClient, - const String &pubTopic = "", - const String &subTopic = "", - bool retain = false, - size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), - _stateUpdater(stateUpdater), - _statefulService(statefulService), - _mqttClient(mqttClient), - _pubTopic(pubTopic), - _subTopic(subTopic), - _retain(retain), - _bufferSize(bufferSize) + MqttEndpoint(JsonStateReader stateReader, + JsonStateUpdater stateUpdater, + StatefulService *statefulService, + PsychicMqttClient *mqttClient, + const String &pubTopic = "", + const String &subTopic = "", + bool retain = false, + size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), + _stateUpdater(stateUpdater), + _statefulService(statefulService), + _mqttClient(mqttClient), + _pubTopic(pubTopic), + _subTopic(subTopic), + _retain(retain), + _bufferSize(bufferSize) { _statefulService->addUpdateHandler([&](const String &originId) { publish(); }, false); - _mqttClient->onConnect(std::bind(&MqttPubSub::onConnect, this)); + _mqttClient->onConnect(std::bind(&MqttEndpoint::onConnect, this)); - _mqttClient->onMessage(std::bind(&MqttPubSub::onMqttMessage, + _mqttClient->onMessage(std::bind(&MqttEndpoint::onMqttMessage, this, std::placeholders::_1, std::placeholders::_2, @@ -160,4 +160,4 @@ class MqttPubSub } }; -#endif // end MqttPubSub +#endif // end MqttEndpoint diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index d2794ced..881e2777 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -38,17 +38,19 @@ static char *retainCstr(const char *cstr, char **ptr) return *ptr; } -MqttSettingsService::MqttSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager), - _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager), - _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE), - _retainedHost(nullptr), - _retainedClientId(nullptr), - _retainedUsername(nullptr), - _retainedPassword(nullptr), - _reconfigureMqtt(false), - _mqttClient(), - _lastError("None") +MqttSettingsService::MqttSettingsService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager), + _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager), + _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE), + _retainedHost(nullptr), + _retainedClientId(nullptr), + _retainedUsername(nullptr), + _retainedPassword(nullptr), + _reconfigureMqtt(false), + _mqttClient(), + _lastError("None") { addUpdateHandler([&](const String &originId) { onConfigUpdated(); }, diff --git a/lib/framework/MqttStatus.cpp b/lib/framework/MqttStatus.cpp index 3efe4b4b..8a56feb8 100644 --- a/lib/framework/MqttStatus.cpp +++ b/lib/framework/MqttStatus.cpp @@ -14,9 +14,11 @@ #include -MqttStatus::MqttStatus(PsychicHttpServer *server, MqttSettingsService *mqttSettingsService, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager), - _mqttSettingsService(mqttSettingsService) +MqttStatus::MqttStatus(PsychicHttpServer *server, + MqttSettingsService *mqttSettingsService, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager), + _mqttSettingsService(mqttSettingsService) { } diff --git a/lib/framework/NTPSettingsService.cpp b/lib/framework/NTPSettingsService.cpp index c3e01698..0e0ffe5d 100644 --- a/lib/framework/NTPSettingsService.cpp +++ b/lib/framework/NTPSettingsService.cpp @@ -14,10 +14,12 @@ #include -NTPSettingsService::NTPSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager), - _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager), - _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) +NTPSettingsService::NTPSettingsService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager), + _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager), + _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) { addUpdateHandler([&](const String &originId) { configureNTP(); }, diff --git a/lib/framework/NotificationEvents.cpp b/lib/framework/NotificationEvents.cpp deleted file mode 100644 index acdc6566..00000000 --- a/lib/framework/NotificationEvents.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include - -NotificationEvents::NotificationEvents(PsychicHttpServer *server) : _server(server) -{ -} - -void NotificationEvents::begin() -{ - _eventSource.onOpen([&](PsychicEventSourceClient *client) { // client->send("hello", NULL, millis(), 1000); -#ifdef SERIAL_INFO - Serial.printf("New client connected to Event Source: #%u connected from %s\n", client->socket(), client->remoteIP().toString()); -#endif - }); - _eventSource.onClose([&](PsychicEventSourceClient *client) { // client->send("hello", NULL, millis(), 1000); -#ifdef SERIAL_INFO - Serial.printf("Client closed connection to Event Source: #%u connected from %s\n", client->socket(), client->remoteIP().toString()); -#endif - }); - _server->on(EVENT_NOTIFICATION_SERVICE_PATH, &_eventSource); - - ESP_LOGV("NotificationEvents", "Registered Event Source endpoint: %s", EVENT_NOTIFICATION_SERVICE_PATH); -} - -void NotificationEvents::pushNotification(String message, pushEvent event, int id) -{ - String eventType; - switch (event) - { - case (PUSHERROR): - eventType = "errorToast"; - break; - case (PUSHWARNING): - eventType = "warningToast"; - break; - case (PUSHINFO): - eventType = "infoToast"; - break; - case (PUSHSUCCESS): - eventType = "successToast"; - break; - default: - return; - } - _eventSource.send(message.c_str(), eventType.c_str(), id); -} - -void NotificationEvents::send(String message, String event, int id) -{ - _eventSource.send(message.c_str(), event.c_str(), id); -} diff --git a/lib/framework/NotificationEvents.h b/lib/framework/NotificationEvents.h deleted file mode 100644 index d33193e9..00000000 --- a/lib/framework/NotificationEvents.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include - -#include -#include - -#define EVENT_NOTIFICATION_SERVICE_PATH "/events" - -enum pushEvent -{ - PUSHERROR, - PUSHWARNING, - PUSHINFO, - PUSHSUCCESS -}; - -class NotificationEvents -{ -protected: - PsychicHttpServer *_server; - PsychicEventSource _eventSource; - -public: - NotificationEvents(PsychicHttpServer *server); - - void begin(); - - void pushNotification(String message, pushEvent event, int id = 0); - - void send(String message, String event, int id = 0); -}; diff --git a/lib/framework/SleepService.cpp b/lib/framework/SleepService.cpp index 77ad04ee..633d675f 100644 --- a/lib/framework/SleepService.cpp +++ b/lib/framework/SleepService.cpp @@ -16,8 +16,9 @@ // Definition of static member variable void (*SleepService::_callbackSleep)() = nullptr; -SleepService::SleepService(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager) +SleepService::SleepService(PsychicHttpServer *server, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager) { } diff --git a/lib/framework/SystemStatus.cpp b/lib/framework/SystemStatus.cpp index 5646e11a..f41848ed 100644 --- a/lib/framework/SystemStatus.cpp +++ b/lib/framework/SystemStatus.cpp @@ -93,8 +93,9 @@ String verbosePrintResetReason(int reason) } } -SystemStatus::SystemStatus(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager) +SystemStatus::SystemStatus(PsychicHttpServer *server, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager) { } diff --git a/lib/framework/UploadFirmwareService.cpp b/lib/framework/UploadFirmwareService.cpp index 4b1ea776..1466c5c1 100644 --- a/lib/framework/UploadFirmwareService.cpp +++ b/lib/framework/UploadFirmwareService.cpp @@ -22,8 +22,9 @@ static char md5[33] = "\0"; static FileType fileType = ft_none; -UploadFirmwareService::UploadFirmwareService(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager) +UploadFirmwareService::UploadFirmwareService(PsychicHttpServer *server, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager) { } diff --git a/lib/framework/WebSocketClient.h.bak b/lib/framework/WebSocketClient.h.bak deleted file mode 100644 index 115d2cf0..00000000 --- a/lib/framework/WebSocketClient.h.bak +++ /dev/null @@ -1,245 +0,0 @@ -#pragma once -/** - * ESP32 SvelteKit - * - * A simple, secure and extensible framework for IoT projects for ESP32 platforms - * with responsive Sveltekit front-end built with TailwindCSS and DaisyUI. - * https://github.com/theelims/ESP32-sveltekit - * - * Copyright (C) 2023 theelims - * - * All Rights Reserved. This software may be modified and distributed under - * the terms of the LGPL v3 license. See the LICENSE file for details. - **/ - -#include -#include -#include -#include -#include "freertos/timers.h" - -#define WEB_SOCKET_CLIENT_ORIGIN "wsclient" - -static const char root_CA[] PROGMEM = R"EOF( ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- -)EOF"; - -static const char *wsTAG = "WS_Client"; - -template -class WebSocketClient -{ -public: - WebSocketClient(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService *statefulService, - const char *webSocketUri, - size_t bufferSize = DEFAULT_BUFFER_SIZE) : _stateReader(stateReader), - _stateUpdater(stateUpdater), - _statefulService(statefulService), - _bufferSize(bufferSize) - { - _statefulService->addUpdateHandler( - [&](const String &originId) - { transmitData(originId); }, - false); - - xTaskCreatePinnedToCore( - this->loopImpl, - "initiateClientTask", - 4096, - this, - tskIDLE_PRIORITY + 1, - NULL, - ESP32SVELTEKIT_RUNNING_CORE); - - wsClientConfig(webSocketUri); - - wsClientConnect(); - } - - boolean wsClientConnected() - { - return esp_websocket_client_is_connected(client); - } - - void wsClientConnect() - { - if (client) - { - esp_websocket_client_start(client); - freshConnected = true; - } - else - { - ESP_LOGE(wsTAG, "Websocket client is not initialized."); - } - } - - void wsClientDisconnect() - { - esp_websocket_client_close(client, pdTICKS_TO_MS(200)); - esp_websocket_client_destroy(client); - client = nullptr; - } - - void wsClientConfig(const char *webSocketUri) - { - if (client) - { - wsClientDisconnect(); - } - - // Configure WS Client - websocket_cfg.uri = "ws://192.168.1.91:1880/ws/request"; - //.uri = "wss://webhook.xtoys.app/ypFXRRx9kzkn?token=91ffdf3434946903a6d34acba97e9b69", - - websocket_cfg.buffer_size = _bufferSize; - - // websocket_cfg.cert_pem = (const char *)root_CA; - // websocket_cfg.cert_len = sizeof(root_CA); - - client = esp_websocket_client_init(&websocket_cfg); - - // Register event handler - esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, &onWSEventStatic, (void *)client); - ESP_LOGI(wsTAG, "Websocket Client configured to %s", websocket_cfg.uri); - } - -protected: - // TimerHandle_t timerHandle; - StatefulService *_statefulService; - esp_websocket_client_handle_t client = nullptr; - esp_websocket_client_config_t websocket_cfg; - size_t _bufferSize; - JsonStateUpdater _stateUpdater; - JsonStateReader _stateReader; - boolean freshConnected = true; - - static void - onWSEventStatic(void *handler_args, - esp_event_base_t base, - int32_t event_id, - void *event_data) - { - // Since this is a static function, we need to cast the first argument (void*) back to the class instance type - WebSocketClient *instance = (WebSocketClient *)handler_args; - instance->onWSEvent(handler_args, base, event_id, event_data); - } - - void onWSEvent(void *handler_args, - esp_event_base_t base, - int32_t event_id, - void *event_data) - { - switch (event_id) - { - case WEBSOCKET_EVENT_DISCONNECTED: - ESP_LOGI(wsTAG, "WEBSOCKET_EVENT_DISCONNECTED"); - // mark as disconnected - freshConnected = true; - break; - case WEBSOCKET_EVENT_ERROR: - ESP_LOGI(wsTAG, "WEBSOCKET_EVENT_ERROR"); - break; - case WEBSOCKET_EVENT_DATA: - - esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; - - ESP_LOGV(wsTAG, "WEBSOCKET_EVENT_DATA"); - ESP_LOGV(wsTAG, "Received opcode=%d", data->op_code); - if (data->op_code == 0x08 && data->data_len == 2) - { - ESP_LOGW(wsTAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]); - } - else - { - // Filter for Text-Messages - if (data->op_code == 0x01) - { - ESP_LOGV(wsTAG, "Total payload length=%d, data_len=%d, current payload offset=%d", data->payload_len, data->data_len, data->payload_offset); - ESP_LOGV(wsTAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); - - // Copy the characters from data->data_ptr to c-string - char payload[_bufferSize]; - strncpy(payload, (char *)data->data_ptr, data->data_len); - payload[data->data_len] = '\0'; - ESP_LOGV(wsTAG, "Payload=%s", payload); - - // DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); - // DeserializationError error = deserializeJson(jsonDocument, payload); - - // ESP_LOGE(wsTAG, "deserializeJson() status: %s", error.c_str()); - - /* if (!error && jsonDocument.is()) - { - JsonObject jsonObject = jsonDocument.as(); - _statefulService->update( - jsonObject, _stateUpdater, WEB_SOCKET_CLIENT_ORIGIN); - ESP_LOGV(wsTAG, "Updated StatefulService"); - } */ - } - } - break; - } - } - - static void loopImpl(void *_this) { static_cast(_this)->loop(); } - - void loop() - { - while (true) - { - // Workaround for a bug in the WEBSOCKET_EVENT_CONNECTED event handler causing a kernel panic. Revisit with ESP-IDF v5.1 - if (wsClientConnected() && freshConnected) - { - ESP_LOGI(wsTAG, "Websocket Client Connected"); - transmitData(WEB_SOCKET_ORIGIN); - freshConnected = false; - } - - vTaskDelay(pdMS_TO_TICKS(150)); - } - } - - void transmitData(const String &originId) - { - DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); - JsonObject payload = jsonDocument.to(); - _statefulService->read(payload, _stateReader); - String json_string; - serializeJson(jsonDocument, json_string); - esp_websocket_client_send_text(client, json_string.c_str(), json_string.length() + 1, portMAX_DELAY); - ESP_LOGI(wsTAG, "Websocket Client Transmission: %s", json_string.c_str()); - } -}; diff --git a/lib/framework/WebSocketServer.h b/lib/framework/WebSocketServer.h index dc7e8e7a..1a325a5b 100644 --- a/lib/framework/WebSocketServer.h +++ b/lib/framework/WebSocketServer.h @@ -74,21 +74,21 @@ class WebSocketServer // when a client connects, we transmit it's id and the current payload transmitId(client); transmitData(client, WEB_SOCKET_ORIGIN); - ESP_LOGI("WebSocketServer", "ws[%s][%u] connect", client->remoteIP().toString(), client->socket()); + ESP_LOGI("WebSocketServer", "ws[%s][%u] connect", client->remoteIP().toString().c_str(), client->socket()); } void onWSClose(PsychicWebSocketClient *client) { - ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString(), client->socket()); + ESP_LOGI("WebSocketServer", "ws[%s][%u] disconnect", client->remoteIP().toString().c_str(), client->socket()); } esp_err_t onWSFrame(PsychicWebSocketRequest *request, httpd_ws_frame *frame) { - ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString(), request->client()->socket(), frame->type); + ESP_LOGV("WebSocketServer", "ws[%s][%u] opcode[%d]", request->client()->remoteIP().toString().c_str(), request->client()->socket(), frame->type); if (frame->type == HTTPD_WS_TYPE_TEXT) { - ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString(), request->client()->socket(), (char *)frame->payload); + ESP_LOGV("WebSocketServer", "ws[%s][%u] request: %s", request->client()->remoteIP().toString().c_str(), request->client()->socket(), (char *)frame->payload); DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); DeserializationError error = deserializeJson(jsonDocument, (char *)frame->payload, frame->len); diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index e9b42cb9..2f1f8384 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -14,8 +14,9 @@ #include -WiFiScanner::WiFiScanner(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager) +WiFiScanner::WiFiScanner(PsychicHttpServer *server, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager) { } diff --git a/lib/framework/WiFiSettingsService.cpp b/lib/framework/WiFiSettingsService.cpp index 97bb5df9..4c510fce 100644 --- a/lib/framework/WiFiSettingsService.cpp +++ b/lib/framework/WiFiSettingsService.cpp @@ -14,23 +14,15 @@ #include -WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, NotificationEvents *notificationEvents) : _server(server), - _securityManager(securityManager), - _httpEndpoint(WiFiSettings::read, - WiFiSettings::update, - this, - server, - WIFI_SETTINGS_SERVICE_PATH, - securityManager, - AuthenticationPredicates::IS_ADMIN, - WIFI_SETTINGS_BUFFER_SIZE), - _fsPersistence(WiFiSettings::read, - WiFiSettings::update, - this, - fs, - WIFI_SETTINGS_FILE), - _lastConnectionAttempt(0), - _notificationEvents(notificationEvents) +WiFiSettingsService::WiFiSettingsService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager, + EventSocket *socket) : _server(server), + _securityManager(securityManager), + _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager, + AuthenticationPredicates::IS_ADMIN, WIFI_SETTINGS_BUFFER_SIZE), + _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE), _lastConnectionAttempt(0), + _socket(socket) { addUpdateHandler([&](const String &originId) { reconfigureWiFiConnection(); }, @@ -57,6 +49,8 @@ void WiFiSettingsService::initWiFi() void WiFiSettingsService::begin() { + _socket->registerEvent(EVENT_RSSI); + _httpEndpoint.begin(); } @@ -220,16 +214,9 @@ void WiFiSettingsService::configureNetwork(wifi_settings_t &network) void WiFiSettingsService::updateRSSI() { - // if WiFi is disconnected send disconnect - if (WiFi.isConnected()) - { - String rssi = String(WiFi.RSSI()); - _notificationEvents->send(rssi, "rssi", millis()); - } - else - { - _notificationEvents->send("disconnected", "rssi", millis()); - } + char buffer[16]; + snprintf(buffer, sizeof(buffer), WiFi.isConnected() ? "%d" : "disconnected", WiFi.RSSI()); + _socket->emit(EVENT_RSSI, buffer); } void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) diff --git a/lib/framework/WiFiSettingsService.h b/lib/framework/WiFiSettingsService.h index c946fb09..986ef667 100644 --- a/lib/framework/WiFiSettingsService.h +++ b/lib/framework/WiFiSettingsService.h @@ -19,10 +19,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -51,6 +51,8 @@ #define WIFI_SETTINGS_BUFFER_SIZE 2048 +#define EVENT_RSSI "rssi" + // Struct defining the wifi settings typedef struct { @@ -201,7 +203,7 @@ class WiFiSettings class WiFiSettingsService : public StatefulService { public: - WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, NotificationEvents *notificationEvents); + WiFiSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket); void initWiFi(); void begin(); @@ -213,7 +215,7 @@ class WiFiSettingsService : public StatefulService SecurityManager *_securityManager; HttpEndpoint _httpEndpoint; FSPersistence _fsPersistence; - NotificationEvents *_notificationEvents; + EventSocket *_socket; unsigned long _lastConnectionAttempt; unsigned long _lastRssiUpdate; diff --git a/lib/framework/WiFiStatus.cpp b/lib/framework/WiFiStatus.cpp index 147fa607..539499dc 100644 --- a/lib/framework/WiFiStatus.cpp +++ b/lib/framework/WiFiStatus.cpp @@ -14,8 +14,9 @@ #include -WiFiStatus::WiFiStatus(PsychicHttpServer *server, SecurityManager *securityManager) : _server(server), - _securityManager(securityManager) +WiFiStatus::WiFiStatus(PsychicHttpServer *server, + SecurityManager *securityManager) : _server(server), + _securityManager(securityManager) { } diff --git a/platformio.ini b/platformio.ini index 66a61d07..6226862f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,16 +24,16 @@ build_flags = ${features.build_flags} -D BUILD_TARGET=\"$PIOENV\" -D APP_NAME=\"ESP32-Sveltekit\" ; Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename - -D APP_VERSION=\"0.3.0\" ; semver compatible version string + -D APP_VERSION=\"0.4.0\" ; semver compatible version string ; Uncomment to receive log messages from the ESP Arduino Core - -D CORE_DEBUG_LEVEL=5 + -D CORE_DEBUG_LEVEL=4 ; Move all networking stuff to the protocol core 0 and leave business logic on application core 1 -D ESP32SVELTEKIT_RUNNING_CORE=0 ; Uncomment EMBED_WWW to embed the WWW data in the firmware binary -D EMBED_WWW ; Uncomment to configure Cross-Origin Resource Sharing - -D ENABLE_CORS - -D CORS_ORIGIN=\"*\" + ; -D ENABLE_CORS + ; -D CORS_ORIGIN=\"*\" ; Serve config files from flash ;-D SERVE_CONFIG_FILES ; Uncomment to enable informations from ESP32-Sveltekit in Serial Monitor @@ -54,6 +54,7 @@ monitor_filters = esp32_exception_decoder default colorize + log2file board_build.filesystem = littlefs extra_scripts = pre:scripts/build_interface.py @@ -63,22 +64,17 @@ lib_deps = ArduinoJson@>=6.0.0,<7.0.0 https://github.com/theelims/PsychicMqttClient.git#0.1.1 -[env:adafruit_feather_esp32_v2] -board = adafruit_feather_esp32_v2 -board_build.mcu = esp32 -board_build.partitions = default_8MB.csv - -[env:lolin_c3_mini] -board = lolin_c3_mini +[env:esp32-c3-devkitm-1] +board = esp32-c3-devkitm-1 board_build.mcu = esp32c3 ; Uncomment min_spiffs.csv setting if using EMBED_WWW with ESP32 board_build.partitions = min_spiffs.csv ; Use USB CDC for firmware upload and serial terminal -board_upload.before_reset = usb_reset -build_flags = - ${env.build_flags} - -DARDUINO_USB_CDC_ON_BOOT=1 - -DARDUINO_USB_MODE=1 +; board_upload.before_reset = usb_reset +; build_flags = +; ${env.build_flags} +; -DARDUINO_USB_CDC_ON_BOOT=1 +; -DARDUINO_USB_MODE=1 [env:esp32-s3-devkitc-1] board = esp32-s3-devkitc-1 @@ -98,4 +94,4 @@ board_build.partitions = min_spiffs.csv build_flags = ${env.build_flags} -D LED_BUILTIN=2 - -D KEY_BUILTIN=0 \ No newline at end of file + -D KEY_BUILTIN=0 diff --git a/src/LightMqttSettingsService.cpp b/src/LightMqttSettingsService.cpp index cde6cf74..8ff2d04a 100644 --- a/src/LightMqttSettingsService.cpp +++ b/src/LightMqttSettingsService.cpp @@ -14,14 +14,20 @@ #include -LightMqttSettingsService::LightMqttSettingsService(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager) : _httpEndpoint(LightMqttSettings::read, - LightMqttSettings::update, - this, - server, - LIGHT_BROKER_SETTINGS_PATH, - securityManager, - AuthenticationPredicates::IS_AUTHENTICATED), - _fsPersistence(LightMqttSettings::read, LightMqttSettings::update, this, fs, LIGHT_BROKER_SETTINGS_FILE) +LightMqttSettingsService::LightMqttSettingsService(PsychicHttpServer *server, + FS *fs, + SecurityManager *securityManager) : _httpEndpoint(LightMqttSettings::read, + LightMqttSettings::update, + this, + server, + LIGHT_BROKER_SETTINGS_PATH, + securityManager, + AuthenticationPredicates::IS_AUTHENTICATED), + _fsPersistence(LightMqttSettings::read, + LightMqttSettings::update, + this, + fs, + LIGHT_BROKER_SETTINGS_FILE) { } diff --git a/src/LightStateService.cpp b/src/LightStateService.cpp index a11b7c26..629888d0 100644 --- a/src/LightStateService.cpp +++ b/src/LightStateService.cpp @@ -15,6 +15,7 @@ #include LightStateService::LightStateService(PsychicHttpServer *server, + EventSocket *socket, SecurityManager *securityManager, PsychicMqttClient *mqttClient, LightMqttSettingsService *lightMqttSettingsService) : _httpEndpoint(LightState::read, @@ -24,7 +25,16 @@ LightStateService::LightStateService(PsychicHttpServer *server, LIGHT_SETTINGS_ENDPOINT_PATH, securityManager, AuthenticationPredicates::IS_AUTHENTICATED), - _mqttPubSub(LightState::homeAssistRead, LightState::homeAssistUpdate, this, mqttClient), + _eventEndpoint(LightState::read, + LightState::update, + this, + socket, + LIGHT_SETTINGS_EVENT, + LIGHT_SETTINGS_MAX_BUFFER_SIZE), + _mqttEndpoint(LightState::homeAssistRead, + LightState::homeAssistUpdate, + this, + mqttClient), _webSocketServer(LightState::read, LightState::update, this, @@ -34,10 +44,6 @@ LightStateService::LightStateService(PsychicHttpServer *server, AuthenticationPredicates::IS_AUTHENTICATED), _mqttClient(mqttClient), _lightMqttSettingsService(lightMqttSettingsService) -/* _webSocketClient(LightState::read, - LightState::update, - this, - LIGHT_SETTINGS_SOCKET_PATH)*/ { // configure led to be output pinMode(LED_BUILTIN, OUTPUT); @@ -59,7 +65,7 @@ LightStateService::LightStateService(PsychicHttpServer *server, void LightStateService::begin() { _httpEndpoint.begin(); - _webSocketServer.begin(); + _eventEndpoint.begin(); _state.ledOn = DEFAULT_LED_STATE; onConfigUpdated(); } @@ -97,5 +103,5 @@ void LightStateService::registerConfig() serializeJson(doc, payload); _mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str()); - _mqttPubSub.configureTopics(pubTopic, subTopic); + _mqttEndpoint.configureTopics(pubTopic, subTopic); } diff --git a/src/LightStateService.h b/src/LightStateService.h index 4c7e2979..15365816 100644 --- a/src/LightStateService.h +++ b/src/LightStateService.h @@ -17,10 +17,11 @@ #include +#include #include -#include +#include +#include #include -// #include #define DEFAULT_LED_STATE false #define OFF_STATE "OFF" @@ -28,6 +29,8 @@ #define LIGHT_SETTINGS_ENDPOINT_PATH "/rest/lightState" #define LIGHT_SETTINGS_SOCKET_PATH "/ws/lightState" +#define LIGHT_SETTINGS_EVENT "led" +#define LIGHT_SETTINGS_MAX_BUFFER_SIZE 256 class LightState { @@ -82,16 +85,18 @@ class LightStateService : public StatefulService { public: LightStateService(PsychicHttpServer *server, + EventSocket *socket, SecurityManager *securityManager, PsychicMqttClient *mqttClient, LightMqttSettingsService *lightMqttSettingsService); + void begin(); private: HttpEndpoint _httpEndpoint; - MqttPubSub _mqttPubSub; + EventEndpoint _eventEndpoint; + MqttEndpoint _mqttEndpoint; WebSocketServer _webSocketServer; - // WebSocketClient _webSocketClient; PsychicMqttClient *_mqttClient; LightMqttSettingsService *_lightMqttSettingsService; diff --git a/src/main.cpp b/src/main.cpp index 326f3634..f35fa5e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,10 +23,12 @@ PsychicHttpServer server; ESP32SvelteKit esp32sveltekit(&server, 120); -LightMqttSettingsService lightMqttSettingsService = - LightMqttSettingsService(&server, esp32sveltekit.getFS(), esp32sveltekit.getSecurityManager()); +LightMqttSettingsService lightMqttSettingsService = LightMqttSettingsService(&server, + esp32sveltekit.getFS(), + esp32sveltekit.getSecurityManager()); LightStateService lightStateService = LightStateService(&server, + esp32sveltekit.getSocket(), esp32sveltekit.getSecurityManager(), esp32sveltekit.getMqttClient(), &lightMqttSettingsService);