diff --git a/.env-sample b/.env-sample index cfb09671..66841738 100644 --- a/.env-sample +++ b/.env-sample @@ -58,6 +58,12 @@ SECRET_KEY = 'django-insecure-6^&6uw$b5^en%(cu2kc7_o)(mgpazx#j_znwlym0vxfamn2uo- # e.g. robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion ONION_LOCATION = '' +# Geoblocked countries (will reject F2F trades). +# List of A3 country codes (see fhttps://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) +# Leave empty '' to allow all countries. +# Example 'NOR,USA,CZE'. +GEOBLOCKED_COUNTRIES = 'ABW,AFG,AGO' + # Link to robosats alternative site (shown in frontend in statsfornerds so users can switch mainnet/testnet) ALTERNATIVE_SITE = 'RoboSats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion' ALTERNATIVE_NAME = 'RoboSats Mainnet' diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index af7e79a3..aa772f1c 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -6,6 +6,15 @@ on: semver: required: true type: string + secrets: + KEYSTORE: + required: true + KEY_ALIAS: + required: true + KEY_PASS: + required: true + KEY_STORE_PASS: + required: true push: branches: [ "main" ] paths: [ "mobile", "frontend" ] @@ -54,10 +63,37 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 + - name: Decode Keystore + id: decode_keystore + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'keystore.jks' + fileDir: './' + encodedString: ${{ secrets.KEYSTORE }} + - name: 'Build Android Release' run: | cd mobile/android ./gradlew assembleRelease + env: + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASS: ${{ secrets.KEY_PASS }} + KEY_STORE_PASS: ${{ secrets.KEY_STORE_PASS }} + + + - name: 'Check for non-FOSS libraries' + run: | + wget https://github.com/iBotPeaches/Apktool/releases/download/v2.7.0/apktool_2.7.0.jar + wget https://github.com/iBotPeaches/Apktool/raw/master/scripts/linux/apktool + # clone the repo + git clone https://gitlab.com/IzzyOnDroid/repo.git + # create a directory for Apktool and move the apktool* files there + mkdir -p repo/lib/radar/tool + mv apktool* repo/lib/radar/tool + # create an alias for ease of use + chmod u+x repo/lib/radar/tool/apktool + mv repo/lib/radar/tool/apktool_2.7.0.jar repo/lib/radar/tool/apktool.jar + repo/bin/scanapk.php mobile/android/app/build/outputs/apk/release/app-universal-release.apk - name: 'Get Commit Hash' id: commit diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 021f8d76..747ace11 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -22,7 +22,7 @@ jobs: matrix: python-tag: ['3.12.3-slim-bookworm', '3.13-rc-slim-bookworm'] lnd-version: ['v0.17.4-beta'] - cln-version: ['v23.11.2','v24.02'] + cln-version: ['v23.11.2'] #,'v24.02'] ln-vendor: ['LND'] #, 'CLN'] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16c823d3..b4fd8a5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,6 +71,7 @@ jobs: android-build: uses: RoboSats/robosats/.github/workflows/android-build.yml@main needs: [frontend-build, check-versions] + secrets: inherit with: semver: ${{ needs.check-versions.outputs.semver }} diff --git a/.gitignore b/.gitignore index 0db3c80f..f5d79e09 100755 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ *.py[cod] __pycache__ -# C extensions -*.so - # Packages *.egg *.egg-info diff --git a/api/admin.py b/api/admin.py index a788eef8..01d4f2d5 100644 --- a/api/admin.py +++ b/api/admin.py @@ -52,8 +52,19 @@ class ETokenAdmin(AdminChangeLinksMixin, TokenAdmin): change_links = ("user",) +class LNPaymentInline(admin.StackedInline): + model = LNPayment + can_delete = True + fields = ("num_satoshis", "status", "routing_budget_sats", "description") + readonly_fields = ("num_satoshis", "status", "routing_budget_sats", "description") + show_change_link = True + show_full_result_count = True + extra = 0 + + @admin.register(Order) class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): + inlines = [LNPaymentInline] list_display = ( "id", "type", @@ -358,8 +369,61 @@ class OrderAdmin(AdminChangeLinksMixin, admin.ModelAdmin): return float(obj.amount) +class OrderInline(admin.StackedInline): + model = Order + can_delete = False + show_change_link = True + extra = 0 + fields = ( + "id", + "type", + "maker", + "taker", + "status", + "amount", + "currency", + "last_satoshis", + "is_disputed", + "is_fiat_sent", + "created_at", + "expires_at", + "payout_tx", + "payout", + "maker_bond", + "taker_bond", + "trade_escrow", + ) + readonly_fields = fields + + +class PayoutOrderInline(OrderInline): + verbose_name = "Order Paid" + fk_name = "payout" + + +class MakerBondOrderInline(OrderInline): + verbose_name = "Order Made" + fk_name = "maker_bond" + + +class TakerBondOrderInline(OrderInline): + verbose_name = "Order Taken" + fk_name = "taker_bond" + + +class EscrowOrderInline(OrderInline): + verbose_name = "Order Escrow" + fk_name = "trade_escrow" + + @admin.register(LNPayment) class LNPaymentAdmin(AdminChangeLinksMixin, admin.ModelAdmin): + inlines = [ + PayoutOrderInline, + MakerBondOrderInline, + TakerBondOrderInline, + EscrowOrderInline, + ] list_display = ( "hash", "concept", diff --git a/api/logics.py b/api/logics.py index 54918014..7049e21c 100644 --- a/api/logics.py +++ b/api/logics.py @@ -1,7 +1,7 @@ import math from datetime import timedelta -from decouple import config +from decouple import config, Csv from django.contrib.auth.models import User from django.db.models import Q, Sum from django.utils import timezone @@ -9,7 +9,7 @@ from django.utils import timezone from api.lightning.node import LNNode from api.models import Currency, LNPayment, MarketTick, OnchainPayment, Order from api.tasks import send_devfund_donation, send_notification -from api.utils import get_minning_fee, validate_onchain_address +from api.utils import get_minning_fee, validate_onchain_address, location_country from chat.models import Message FEE = float(config("FEE")) @@ -29,6 +29,8 @@ MAX_MINING_NETWORK_SPEEDUP_EXPECTED = float( config("MAX_MINING_NETWORK_SPEEDUP_EXPECTED") ) +GEOBLOCKED_COUNTRIES = config("GEOBLOCKED_COUNTRIES", cast=Csv(), default="") + class Logics: @classmethod @@ -137,6 +139,19 @@ class Logics: return True, None + @classmethod + def validate_location(cls, order) -> bool: + if not (order.latitude or order.longitude): + return True, None + + country = location_country(order.longitude, order.latitude) + if country in GEOBLOCKED_COUNTRIES: + return False, { + "bad_request": f"The coordinator does not support orders in {country}" + } + else: + return True, None + def validate_amount_within_range(order, amount): if amount > float(order.max_amount) or amount < float(order.min_amount): return False, { @@ -878,7 +893,7 @@ class Logics: if order.status == Order.Status.FAI: if order.payout.status != LNPayment.Status.EXPIRE: return False, { - "bad_request": "You can only submit an invoice after expiration or 3 failed attempts" + "bad_invoice": "You can only submit an invoice after expiration or 3 failed attempts" } # cancel onchain_payout if existing @@ -894,25 +909,24 @@ class Logics: if not payout["valid"]: return False, payout["context"] - order.payout, _ = LNPayment.objects.update_or_create( + if order.payout: + if order.payout.payment_hash == payout["payment_hash"]: + return False, {"bad_invoice": "You must submit a NEW invoice"} + + order.payout = LNPayment.objects.create( concept=LNPayment.Concepts.PAYBUYER, type=LNPayment.Types.NORM, sender=User.objects.get(username=ESCROW_USERNAME), - # In case this user has other payouts, update the one related to this order. - order_paid_LN=order, receiver=user, routing_budget_ppm=routing_budget_ppm, routing_budget_sats=routing_budget_sats, - # if there is a LNPayment matching these above, it updates that one with defaults below. - defaults={ - "invoice": invoice, - "status": LNPayment.Status.VALIDI, - "num_satoshis": num_satoshis, - "description": payout["description"], - "payment_hash": payout["payment_hash"], - "created_at": payout["created_at"], - "expires_at": payout["expires_at"], - }, + invoice=invoice, + status=LNPayment.Status.VALIDI, + num_satoshis=num_satoshis, + description=payout["description"], + payment_hash=payout["payment_hash"], + created_at=payout["created_at"], + expires_at=payout["expires_at"], ) order.is_swap = False diff --git a/api/models/currency.py b/api/models/currency.py index b9713bc2..1398f2d8 100644 --- a/api/models/currency.py +++ b/api/models/currency.py @@ -1,5 +1,6 @@ import json +from decimal import Decimal from django.core.validators import MinValueValidator from django.db import models from django.utils import timezone @@ -18,7 +19,7 @@ class Currency(models.Model): decimal_places=4, default=None, null=True, - validators=[MinValueValidator(0)], + validators=[MinValueValidator(Decimal(0))], ) timestamp = models.DateTimeField(default=timezone.now) diff --git a/api/models/market_tick.py b/api/models/market_tick.py index ac09d360..ed4020a9 100644 --- a/api/models/market_tick.py +++ b/api/models/market_tick.py @@ -1,5 +1,6 @@ import uuid +from decimal import Decimal from decouple import config from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -27,21 +28,24 @@ class MarketTick(models.Model): decimal_places=2, default=None, null=True, - validators=[MinValueValidator(0)], + validators=[MinValueValidator(Decimal(0))], ) volume = models.DecimalField( max_digits=8, decimal_places=8, default=None, null=True, - validators=[MinValueValidator(0)], + validators=[MinValueValidator(Decimal(0))], ) premium = models.DecimalField( max_digits=5, decimal_places=2, default=None, null=True, - validators=[MinValueValidator(-100), MaxValueValidator(999)], + validators=[ + MinValueValidator(Decimal(-100)), + MaxValueValidator(Decimal(999)) + ], blank=True, ) currency = models.ForeignKey("api.Currency", null=True, on_delete=models.SET_NULL) @@ -52,7 +56,10 @@ class MarketTick(models.Model): max_digits=4, decimal_places=4, default=0, - validators=[MinValueValidator(0), MaxValueValidator(1)], + validators=[ + MinValueValidator(Decimal(0)), + MaxValueValidator(Decimal(1)) + ], ) def log_a_tick(order): diff --git a/api/models/onchain_payment.py b/api/models/onchain_payment.py index c6959973..83c0a452 100644 --- a/api/models/onchain_payment.py +++ b/api/models/onchain_payment.py @@ -1,3 +1,4 @@ +from decimal import Decimal from django.conf import settings from django.contrib.auth.models import User from django.core.validators import MaxValueValidator, MinValueValidator @@ -58,7 +59,10 @@ class OnchainPayment(models.Model): default=2.05, null=False, blank=False, - validators=[MinValueValidator(1), MaxValueValidator(999)], + validators=[ + MinValueValidator(Decimal(1)), + MaxValueValidator(Decimal(999)) + ], ) mining_fee_rate = models.DecimalField( max_digits=6, @@ -66,7 +70,10 @@ class OnchainPayment(models.Model): default=2.05, null=False, blank=False, - validators=[MinValueValidator(1), MaxValueValidator(999)], + validators=[ + MinValueValidator(Decimal(1)), + MaxValueValidator(Decimal(999)) + ], ) mining_fee_sats = models.PositiveBigIntegerField(default=0, null=False, blank=False) diff --git a/api/models/order.py b/api/models/order.py index 50161281..161f6d3e 100644 --- a/api/models/order.py +++ b/api/models/order.py @@ -1,6 +1,7 @@ # We use custom seeded UUID generation during testing import uuid +from decimal import Decimal from decouple import config from django.conf import settings from django.contrib.auth.models import User @@ -90,7 +91,10 @@ class Order(models.Model): decimal_places=2, default=0, null=True, - validators=[MinValueValidator(-100), MaxValueValidator(999)], + validators=[ + MinValueValidator(Decimal(-100)), + MaxValueValidator(Decimal(999)) + ], blank=True, ) # explicit @@ -135,8 +139,8 @@ class Order(models.Model): default=settings.DEFAULT_BOND_SIZE, null=False, validators=[ - MinValueValidator(settings.MIN_BOND_SIZE), # 2 % - MaxValueValidator(settings.MAX_BOND_SIZE), # 15 % + MinValueValidator(Decimal(settings.MIN_BOND_SIZE)), # 2 % + MaxValueValidator(Decimal(settings.MAX_BOND_SIZE)), # 15 % ], blank=False, ) @@ -147,8 +151,8 @@ class Order(models.Model): decimal_places=6, null=True, validators=[ - MinValueValidator(-90), - MaxValueValidator(90), + MinValueValidator(Decimal(-90)), + MaxValueValidator(Decimal(90)), ], blank=True, ) @@ -157,8 +161,8 @@ class Order(models.Model): decimal_places=6, null=True, validators=[ - MinValueValidator(-180), - MaxValueValidator(180), + MinValueValidator(Decimal(-180)), + MaxValueValidator(Decimal(180)), ], blank=True, ) diff --git a/api/oas_schemas.py b/api/oas_schemas.py index 2c611f84..8b6412e6 100644 --- a/api/oas_schemas.py +++ b/api/oas_schemas.py @@ -219,14 +219,17 @@ class OrderViewSchema: - `update_invoice` - This action only is valid if you are the buyer. The `invoice` field needs to be present in the body and the value must be a - valid LN invoice as cleartext PGP message signed with the robot key. Make sure to perform this action only when + valid LN invoice as cleartext PGP message signed (SHA512) with the robot key. + The amount of the invoice should be `invoice_amount` minus the routing + budget whose parts per million should be specified by `routing_budget_ppm`. + Make sure to perform this action only when both the bonds are locked. i.e The status of your order is at least `6` (Waiting for trade collateral and buyer invoice) - `update_address` - This action is only valid if you are the buyer. This action is used to set an on-chain payout address if you wish to have your payout be received on-chain. Only valid if there is an address in the body as - cleartext PGP message signed with the robot key. This enables on-chain swap for the + cleartext PGP message signed (SHA512) with the robot key. This enables on-chain swap for the order, so even if you earlier had submitted a LN invoice, it will be ignored. You get to choose the `mining_fee_rate` as well. Mining fee rate is specified in sats/vbyte. @@ -246,9 +249,7 @@ class OrderViewSchema: mid-trade so use this action carefully: - As a maker if you cancel an order after you have locked your - maker bond, you are returned your bond. This may change in - the future to prevent DDoSing the LN node and you won't be - returned the maker bond. + maker bond, you are returned your bond. - As a taker there is a time penalty involved if you `take` an order and cancel it without locking the taker bond. - For both taker or maker, if you cancel the order when both @@ -387,12 +388,13 @@ class RobotViewSchema: An authenticated request (has the token's sha256 hash encoded as base 91 in the Authorization header) will be returned the information about the state of a robot. - Make sure you generate your token using cryptographically secure methods. [Here's]() the function the Javascript - client uses to generate the tokens. Since the server only receives the hash of the + Make sure you generate your token using cryptographically secure methods. + Since the server only receives the hash of the token, it is responsibility of the client to create a strong token. Check - [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.js) + [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.ts) to see how the Javascript client creates a random strong token and how it validates entropy is optimal for tokens created by the user at will. + The PGP key should be an EdDSA ed25519/cert,sign+cv25519/encr key. `public_key` - PGP key associated with the user (Armored ASCII format) `encrypted_private_key` - Private PGP key. This is only stored on the backend for later fetching by @@ -403,7 +405,7 @@ class RobotViewSchema: A gpg key can be created by: ```shell - gpg --full-gen-key + gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" --full-gen-key ``` it's public key can be exported in ascii armored format with: @@ -531,7 +533,7 @@ class InfoViewSchema: class RewardViewSchema: post = { "summary": "Withdraw reward", - "description": "Withdraw user reward by submitting an invoice. The invoice must be send as cleartext PGP message signed with the robot key", + "description": "Withdraw user reward by submitting an invoice. The invoice must be send as cleartext PGP message signed (SHA512) with the robot key", "responses": { 200: { "type": "object", diff --git a/api/serializers.py b/api/serializers.py index 6094e600..4bd41fd2 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -494,7 +494,8 @@ class OrderPublicSerializer(serializers.ModelSerializer): maker_nick = serializers.CharField(required=False) maker_hash_id = serializers.CharField(required=False) maker_status = serializers.CharField( - help_text='Status of the nick - "Active" or "Inactive"', required=False + help_text='Status of the nick - "Active", "Seen Recently" or "Inactive"', + required=False, ) price = serializers.FloatField( help_text="Price in order's fiat currency", required=False diff --git a/api/utils.py b/api/utils.py index 85b5dbcf..13fad5b0 100644 --- a/api/utils.py +++ b/api/utils.py @@ -479,6 +479,33 @@ def is_valid_token(token: str) -> bool: return all(c in charset for c in token) +def location_country(lon: float, lat: float) -> str: + """ + Returns the country code of a lon/lat location + """ + + from shapely.geometry import shape, Point + from shapely.prepared import prep + + # Load the GeoJSON data from a local file + with open("frontend/static/assets/geo/countries-coastline-10km.geo.json") as f: + countries_geojeson = json.load(f) + + # Prepare the countries for reverse geocoding + countries = {} + for feature in countries_geojeson["features"]: + geom = feature["geometry"] + country_code = feature["properties"]["A3"] + countries[country_code] = prep(shape(geom)) + + point = Point(lon, lat) + for country_code, geom in countries.items(): + if geom.contains(point): + return country_code + + return "unknown" + + def objects_to_hyperlinks(logs: str) -> str: """ Parses strings that have Object(ID,NAME) that match API models. diff --git a/api/views.py b/api/views.py index 3a76dc7a..49a21a2c 100644 --- a/api/views.py +++ b/api/views.py @@ -162,6 +162,10 @@ class MakerView(CreateAPIView): if not valid: return Response(context, status.HTTP_400_BAD_REQUEST) + valid, context = Logics.validate_location(order) + if not valid: + return Response(context, status.HTTP_400_BAD_REQUEST) + order.save() order.log( f"Order({order.id},{order}) created by Robot({request.user.robot.id},{request.user})" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 4a085514..e351aeea 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -90,7 +90,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) jekyll (3.9.5) addressable (~> 2.4) @@ -213,7 +213,7 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minimal-mistakes-jekyll (4.24.0) + minimal-mistakes-jekyll (4.25.1) jekyll (>= 3.7, < 5.0) jekyll-feed (~> 0.1) jekyll-gist (~> 1.5) @@ -230,7 +230,7 @@ GEM sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (5.0.4) + public_suffix (5.0.5) racc (1.7.3) rb-fsevent (0.11.2) rb-inotify (0.10.1) diff --git a/docs/_pages/docs/01-best-practices/02-payment-methods.md b/docs/_pages/docs/01-best-practices/02-payment-methods.md index 174a77f5..31b2cefa 100644 --- a/docs/_pages/docs/01-best-practices/02-payment-methods.md +++ b/docs/_pages/docs/01-best-practices/02-payment-methods.md @@ -62,6 +62,14 @@ In Canada, [Interac e-Transfer](https://www.interac.ca/en/consumers/support/faq- The best practice for users trying to transact with a payment method with a high risk of losing funds is discussed in this section. +### Instant SEPA Payment Guidelines + +Instant SEPA is a widely adopted payment method across Europe, offering fast and efficient cashless transactions. However, it comes with a significant risk for sellers, including the potential for chargebacks. To mitigate these risks, it is advisable for sellers to request the buyer's information before sharing their SEPA details. This information could include the buyer's country, full name, and bank account number. By obtaining this information, sellers can reduce the risk of fraudulent transactions, such as triangle attacks, while buyers, sharing this information does not decrease their privacy, as they are not exposing any additional information that the seller would not have access to anyway after the SEPA transfer. + +For buyers, it is crucial to comply with sellers' if they request personal information when they are initiating SEPA transactions. Failure to provide this information can lead to the seller raising an immediate dispute, which sellers are likely to win (the seller will also earn the buyer's bond in this specific case). Therefore, it is in the best interest of buyers to cooperate with sellers' requests for information. + +Sellers are encouraged to share a link to this guide with their buyers when requesting information. This ensures that both parties are informed and understand the importance of this step when using Instant SEPA. + ### Revolut via payment links In a Revolut payment, a `@revtag` is usually exchanged in the chat and can be verified in the payment history of the app making proof of payments easy. @@ -70,12 +78,14 @@ However, payment links, which have the format https://revolut.me/p/XXXXX, don't In a dispute, there's no recipient address reference and both buyer and seller could cheat. The payment link could be redeemed by an unknown third party complicit with either buyer or seller. -Therefore, insist on receiving the `@revtag` when making a payment with Revolut to avoid these risks. The `@revtag` can also be received as a link. This link would look like this: https://revolut.me/@revtag. +Therefore, insist on receiving the `@revtag` when making a payment with Revolut to avoid these risks. The `@revtag` can also be received as a link. This link would look like this: https://revolut.me/@revtag. ### Paypal -Paypal is one of the widely used fiat payment methods. However, with PayPal buyer protection policy, buyer can do fraudulent action by creating a refund request in PayPal after the trading process in RoboSats is finished and therefore taking both fiat and bitcoin all by themselves. +Paypal is one of the widely used fiat payment methods. However, as a seller Paypal is the highest risk you can take. Using Paypal as payment method is not advised. -This fraud can be prevented by agreeing with the buyer to have them send money using the “send money to a friend or family member” option. This will make the buyer become the one liable for the transaction fee and make it less likely for them to request a refund. +If you still wish to use Paypal there is a few things to take into account. With PayPal buyer protection policy, buyers can do fraudulent action by creating a refund request in PayPal after the trading process in RoboSats is finished and therefore taking both fiat and bitcoin all by themselves. + +This fraud could be prevented by agreeing with the buyer to have them send money using the “send money to a friend or family member” option. This will make the buyer become the one liable for the transaction fee and make it less likely for them to request a refund. ### For seller If you are a seller and your peer both agreed to use “send money to a friend or family member” but your peer used the "send money for Goods or Services" option, you should return the fiat payment and ask your peer to send with an agreed method. If they insist to break the agreement, you may ask them to voluntarily end the trade or end the trade by calling a dispute. @@ -83,26 +93,4 @@ If you are a seller and your peer both agreed to use “send money to a friend o ### For buyer If you are a buyer and you need to use “send money to a friend or family member” to pay fiat to your peer, you can choose the specified payment type by following these steps. -#### PayPal Desktop -In PayPal desktop, it is located below the drop-down currency list, it should be labeled as "Sending to a friend". -If it is labeled otherwise, you'll need to click "Change" on the right to change the payment type. -
- -
-Then select "Sending to a friend" in the payment type choosing page. -
- -
- -#### PayPal Mobile -In PayPal mobile, it is located below the payment method (In this case is VISA), it should be labeled as "Friends or Family". -If it is labeled otherwise, you'll need to tab ">" on the right to change the payment type. -
- -
-Then select "Friends or Family" in the payment type choosing page. -
- -
- {% include improve %} diff --git a/docs/_pages/docs/03-understand/07-wallets.md b/docs/_pages/docs/03-understand/07-wallets.md index b03d3e1d..1907d149 100644 --- a/docs/_pages/docs/03-understand/07-wallets.md +++ b/docs/_pages/docs/03-understand/07-wallets.md @@ -28,15 +28,17 @@ This is a non-exhaustive compilation based on past experience of users. We have | Wallet | Version | Device | UX1 | Bonds2 | Payout3 | Comp4 | Total5 | |:---|:---|:--:|:--:|:--:|:--:|:--:|:--:| |[Alby](#alby-browser-extension)|[v1.14.2](https://github.com/getAlby/lightning-browser-extension)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| +|[Aqua](#aqua-mobile)|[v0.1.55](https://aquawallet.io/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.unclear}}|{{page.good}} |{{page.thumbsup}}| |[Blink](#blink-mobile-former-bitcoin-beach-wallet)|[2.2.73](https://www.blink.sv/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| |[Blixt](#blixt-androidios-lnd-light-backend-on-device)|[v0.4.1](https://github.com/hsjoberg/blixt-wallet)|{{page.phone}}|{{page.soso}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Blue](#bluewallet-mobile)|[1.4.4](https://bluewallet.io/)|{{page.phone}}|{{page.good}}|{{page.unclear}}|{{page.unclear}}|{{page.good}}|{{page.unclear}}| |[Breez](#breez-mobile)|[0.16](https://breez.technology/mobile/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Cash App](#cash-app-mobile)|[4.7](https://cash.app/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} |{{page.thumbsup}}| |[Core Lightning](#core-lightning--cln-cli-interface)|[v0.11.1](https://github.com/ElementsProject/lightning)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| -|[Electrum](#electrum-desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|| +|[Electrum](#electrum-mobile--desktop)|[4.1.4](https://github.com/spesmilo/electrum)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}|| |[LND](#lnd-cli-interface)|[v0.14.2](https://github.com/LightningNetwork/lnd)|{{page.cli}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| |[Mash](https://app.mash.com/wallet)|[Beta](https://mash.com/consumer-experience/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}} | {{page.thumbsup}}| +|[Mutiny](#mutiny-mobile--web-browser-wallet)|[1.7.1](https://www.mutinywallet.com/)|{{page.laptop}}{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsdown}}|| |[Muun](#muun-mobile)|[47.3](https://muun.com/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.bad}}|{{page.bad}}|{{page.thumbsdown}}| |[Phoenix](#phoenix-mobile)|[35-1.4.20](https://phoenix.acinq.co/)|{{page.phone}}|{{page.good}}|{{page.soso}}|{{page.soso}}|{{page.soso}}|{{page.unclear}}| |[SBW](https://github.com/RoboSats/robosats/issues/44#issue-1135544303)|[2.4.27](https://github.com/btcontract/wallet/)|{{page.phone}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.good}}|{{page.thumbsup}}| @@ -57,6 +59,12 @@ Instructions to install Alby in Tor Browser: 1. Install the Alby extension from the [Firefox add-ons store](https://addons.mozilla.org/en-US/firefox/addon/alby/) 2. Click on the Alby extension and follow the prompts to setup your wallet. +### Aqua (Mobile) +Overall the wallet works as expected. Hold invoices work reliably. +What is inconvenient: +- Lightning payments are encapsulated into Liquid Bitcoin so there is a small additional fee for conversion in/out +- Bond refund is locked for 3 days + ### Blink (Mobile, former Bitcoin Beach Wallet) Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transaction history. Payouts to the Blink wallet function as intended. Custodial wallet by Galoy which originated from the Bitcoin Beach project in El Salvador (formerly known as "Bitcoin Beach Wallet"). @@ -75,8 +83,11 @@ Works well with RoboSats. Hodl invoices (Bonds) show as "Pending" in the transac ### Core Lightning / CLN (CLI Interface) Works as expected. The `lightning-cli pay ` command does not conclude while the payment is pending, but can use `lightning-cli paystatus ` to monitor the state. -### Electrum (Desktop) -Works as expected. Some payments and locks may fail depending on the Lightning node the channel is created to. Channels to ASINQ work fine. +### Electrum (Mobile & Desktop) +Overall the wallet works as expected. The interface is precise and clear. +What is inconvenient: +- all your Lightning channels have to be created to the node: ACINQ + ### LND (CLI Interface) Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not user friendly and therefore not recommended to interact with RoboSats by beginners. However, everything works just fine. If you are using LNCLI regularly, then you will find no issue using it with RoboSats. @@ -85,6 +96,13 @@ Raw; it shows exactly what is happening and what it knows "IN_FLIGHT". It is not ### Mash Wallet App (Mobile PWA & Desktop Web-Wallet) Overall the [Mash](https://mash.com/consumer-experience/) wallet works end2end with Robosats on both selling & buying over lightning. Majority of relevant invoice details in the mash wallet are shown and clear to users throughout the process. When the transactions are complete, they open in the mobile app on both sender/receiver sides to highlight that the transactions are completed.The one UX hick-up is that the pending invoices list doesn't explicitly show HOLD invoices and there is a "spinning" screen on first HOLD invoice payment. The team has a bug open to fix this issue shortly (this note is from Aug 21st 2023). +### Mutiny (Mobile & Web Browser Wallet) +The wallet should work as expected, but the interface, transaction states, and the structure of the funds can sometimes be very confusing in the current release version. +Use the default free Fedimint(Chaumian eCash) account, with the possibility to use zero fee Lightning transfers. +What is inconvenient: +- occasionally wallet restart is needed +- more than two pending hold invoices at the same time may cause a rejection of the new transaction + ### Muun (Mobile) Similar to Blixt or LND, Muun plays nicely with hold invoices. You can be a seller in RoboSats using Muun and the user experience will be great. However, in order to be a buyer when using Muun, you need to submit an on-chain address for the payout as a Lightning invoice won't work. Muun is _fee siphoning attacking_ any sender to Muun wallet. There is a mandatory hop through a private channel with a fee of +1500ppm. RoboSats will strictly not route a buyer payout for a net loss. Given that RoboSats trading fees are {{site.robosats.total_fee}}% and it needs to cover the routing fees, **RoboSats will never find a suitable route to a Muun wallet user**. At the moment, RoboSats will scan your invoice for routing hints that can potentially encode a _fee siphoning attack_. If this trick is found, then the invoice will be rejected: submit an on-chain address instead for an on-the-fly swap. Refer to [Understand > On-Chain Payouts](/docs/on-chain-payouts/) for more information about on-the-fly swaps. Important to note that Muun has issues during times of high on chain fee spikes. Regardless, the workaround to receive to Muun is: either submit an on chain address or choose a higher routing budget after enabling the "Advanced Options" switch. @@ -93,6 +111,7 @@ One of the simplest and one of the best. The hodl invoice shows as "on fly", it *Update 26-10-23: At this moment it has no development or support ### Phoenix (Mobile) +DEV team stated that they do not want to support hold invoices. *Update 21-10-23. Phoenix used to work as described here, but many things changed to worse with the last update of the wallet. Phoenix works very well as an order taker. Phoenix will also work well as an order maker as long as the order settings `public duration` + `deposit duration` are lower than 10 hours. Otherwise, you might have problems locking the maker bond. If the total duraton of bonds/escrow invoices exceeds 450 blocks, then Phoenix will not allow users to lock the bond (`Cannot add htlc (...) reason=expiry too big`). diff --git a/docs/assets/schemas/api-latest.yaml b/docs/assets/schemas/api-latest.yaml index b6a64a29..67735029 100644 --- a/docs/assets/schemas/api-latest.yaml +++ b/docs/assets/schemas/api-latest.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: RoboSats REST API - version: 0.6.0 + version: 0.6.2 x-logo: url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png backgroundColor: '#FFFFFF' @@ -443,14 +443,17 @@ paths: - `update_invoice` - This action only is valid if you are the buyer. The `invoice` field needs to be present in the body and the value must be a - valid LN invoice as cleartext PGP message signed with the robot key. Make sure to perform this action only when + valid LN invoice as cleartext PGP message signed (SHA512) with the robot key. + The amount of the invoice should be `invoice_amount` minus the routing + budget whose parts per million should be specified by `routing_budget_ppm`. + Make sure to perform this action only when both the bonds are locked. i.e The status of your order is at least `6` (Waiting for trade collateral and buyer invoice) - `update_address` - This action is only valid if you are the buyer. This action is used to set an on-chain payout address if you wish to have your payout be received on-chain. Only valid if there is an address in the body as - cleartext PGP message signed with the robot key. This enables on-chain swap for the + cleartext PGP message signed (SHA512) with the robot key. This enables on-chain swap for the order, so even if you earlier had submitted a LN invoice, it will be ignored. You get to choose the `mining_fee_rate` as well. Mining fee rate is specified in sats/vbyte. @@ -470,9 +473,7 @@ paths: mid-trade so use this action carefully: - As a maker if you cancel an order after you have locked your - maker bond, you are returned your bond. This may change in - the future to prevent DDoSing the LN node and you won't be - returned the maker bond. + maker bond, you are returned your bond. - As a taker there is a time penalty involved if you `take` an order and cancel it without locking the taker bond. - For both taker or maker, if you cancel the order when both @@ -631,7 +632,7 @@ paths: post: operationId: reward_create description: Withdraw user reward by submitting an invoice. The invoice must - be send as cleartext PGP message signed with the robot key + be send as cleartext PGP message signed (SHA512) with the robot key summary: Withdraw reward tags: - reward @@ -721,12 +722,13 @@ paths: An authenticated request (has the token's sha256 hash encoded as base 91 in the Authorization header) will be returned the information about the state of a robot. - Make sure you generate your token using cryptographically secure methods. [Here's]() the function the Javascript - client uses to generate the tokens. Since the server only receives the hash of the + Make sure you generate your token using cryptographically secure methods. + Since the server only receives the hash of the token, it is responsibility of the client to create a strong token. Check - [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.js) + [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.ts) to see how the Javascript client creates a random strong token and how it validates entropy is optimal for tokens created by the user at will. + The PGP key should be an EdDSA ed25519/cert,sign+cv25519/encr key. `public_key` - PGP key associated with the user (Armored ASCII format) `encrypted_private_key` - Private PGP key. This is only stored on the backend for later fetching by @@ -737,7 +739,7 @@ paths: A gpg key can be created by: ```shell - gpg --full-gen-key + gpg --default-new-key-algo "ed25519/cert,sign+cv25519/encr" --full-gen-key ``` it's public key can be exported in ascii armored format with: @@ -1636,7 +1638,7 @@ components: type: string maker_status: type: string - description: Status of the nick - "Active" or "Inactive" + description: Status of the nick - "Active", "Seen Recently" or "Inactive" price: type: number format: double diff --git a/docs/assets/schemas/api-v0.1.yaml b/docs/assets/schemas/api-v0.1.yaml index 7e6f2ed0..e9b933a0 100644 --- a/docs/assets/schemas/api-v0.1.yaml +++ b/docs/assets/schemas/api-v0.1.yaml @@ -1539,7 +1539,7 @@ components: type: string maker_status: type: string - description: Status of the nick - "Active" or "Inactive" + description: Status of the nick - "Active", "Seen Recently" or "Inactive" price: type: integer description: Price in order's fiat currency diff --git a/docs/assets/schemas/api-v0.5.3.yaml b/docs/assets/schemas/api-v0.5.3.yaml index 51d53a67..d3c21935 100644 --- a/docs/assets/schemas/api-v0.5.3.yaml +++ b/docs/assets/schemas/api-v0.5.3.yaml @@ -1712,7 +1712,7 @@ components: type: string maker_status: type: string - description: Status of the nick - "Active" or "Inactive" + description: Status of the nick - "Active", "Seen Recently" or "Inactive" price: type: number format: double diff --git a/fastlane/metadata/en-US/full_description.txt b/fastlane/metadata/en-US/full_description.txt new file mode 100644 index 00000000..8b31ea67 --- /dev/null +++ b/fastlane/metadata/en-US/full_description.txt @@ -0,0 +1,12 @@ +

RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.

+


Features:

+

You can join other cool Robots and get community support at our Telegram group.

\ No newline at end of file diff --git a/fastlane/metadata/en-US/images/icon.png b/fastlane/metadata/en-US/images/icon.png new file mode 100644 index 00000000..1acd5bd2 Binary files /dev/null and b/fastlane/metadata/en-US/images/icon.png differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg new file mode 100644 index 00000000..0e8c2168 Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg new file mode 100644 index 00000000..fe0fa90f Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg new file mode 100644 index 00000000..b900c054 Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg new file mode 100644 index 00000000..1fa2a63b Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg new file mode 100644 index 00000000..ebe70da6 Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg differ diff --git a/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg b/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg new file mode 100644 index 00000000..b44133af Binary files /dev/null and b/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg differ diff --git a/fastlane/metadata/en-US/short_description.txt b/fastlane/metadata/en-US/short_description.txt new file mode 100644 index 00000000..a5607740 --- /dev/null +++ b/fastlane/metadata/en-US/short_description.txt @@ -0,0 +1 @@ +Simple and private app to exchange bitcoin for national currencies. \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 33ee268c..ec3cec07 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.6.0", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.6.0", + "version": "0.6.2", "license": "ISC", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", @@ -16,12 +16,12 @@ "@mui/base": "^5.0.0-beta.7", "@mui/icons-material": "^5.15.9", "@mui/lab": "^5.0.0-alpha.136", - "@mui/material": "^5.15.9", + "@mui/material": "^5.15.14", "@mui/system": "^5.15.11", - "@mui/x-data-grid": "^7.3.0", + "@mui/x-data-grid": "^7.6.0", "@mui/x-date-pickers": "^7.2.0", - "@nivo/core": "^0.85.1", - "@nivo/line": "^0.85.1", + "@nivo/core": "^0.86.0", + "@nivo/line": "^0.86.0", "base-ex": "^0.8.1", "country-flag-icons": "^1.5.11", "date-fns": "^2.30.0", @@ -79,7 +79,7 @@ "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^29.6.1", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "ts-node": "^10.9.2", "typescript": "^5.4.2", "webpack": "^5.89.0", @@ -108,12 +108,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -171,11 +171,11 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz", + "integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -212,18 +212,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", - "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz", + "integrity": "sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-annotate-as-pure": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-member-expression-to-functions": "^7.24.6", + "@babel/helper-optimise-call-expression": "^7.24.6", + "@babel/helper-replace-supers": "^7.24.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", "semver": "^6.3.1" }, "engines": { @@ -267,20 +267,20 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -298,37 +298,37 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz", + "integrity": "sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -338,20 +338,20 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz", + "integrity": "sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz", + "integrity": "sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg==", "engines": { "node": ">=6.9.0" } @@ -374,13 +374,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz", + "integrity": "sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-member-expression-to-functions": "^7.24.6", + "@babel/helper-optimise-call-expression": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -390,58 +390,58 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz", + "integrity": "sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", + "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "engines": { "node": ">=6.9.0" } @@ -474,22 +474,23 @@ } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -702,12 +703,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz", + "integrity": "sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -819,12 +819,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz", + "integrity": "sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -1206,14 +1205,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", - "dev": true, + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.6.tgz", + "integrity": "sha512-JEV8l3MHdmmdb7S7Cmx6rbNEjRCgTQMZxllveHO0mx6uiclB0NflCawlQQ6+o5ZrwjUBYPzHm2XoK4wqGVUFuw==", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helper-plugin-utils": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -1644,15 +1642,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz", - "integrity": "sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg==", - "dev": true, + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.6.tgz", + "integrity": "sha512-H0i+hDLmaYYSt6KU9cZE0gb3Cbssa/oxWis7PX4ofQzbvsfix9Lbh8SRk7LCPDlLWJHUiFeHU0qRRpF/4Zv7mQ==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.9", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.24.6", + "@babel/helper-create-class-features-plugin": "^7.24.6", + "@babel/helper-plugin-utils": "^7.24.6", + "@babel/plugin-syntax-typescript": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -1855,16 +1852,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", - "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", - "dev": true, + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.6.tgz", + "integrity": "sha512-U10aHPDnokCFRXgyT/MaIRTivUu2K/mu0vJlwRS9LxJmJet+PFQNKpggPyFCUtC6zWSBPjvxjnpNkAn3Uw2m5w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-typescript": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", + "@babel/plugin-syntax-jsx": "^7.24.6", + "@babel/plugin-transform-modules-commonjs": "^7.24.6", + "@babel/plugin-transform-typescript": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -1873,6 +1869,141 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -1880,9 +2011,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz", + "integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1891,13 +2022,13 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -1924,12 +2055,12 @@ } }, "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", + "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2039,14 +2170,14 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", + "@emotion/serialize": "^1.1.3", "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", "@emotion/utils": "^1.2.1", "@emotion/weak-memoize": "^0.3.1", @@ -2062,9 +2193,9 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", "dependencies": { "@emotion/hash": "^0.9.1", "@emotion/memoize": "^0.8.1", @@ -3073,6 +3204,39 @@ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, + "node_modules/@mnajdova/enzyme-adapter-react-18": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mnajdova/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.2.0.tgz", + "integrity": "sha512-BOnjlVa7FHI1YUnYe+FdUtQu6szI1wLJ+C1lHyqmF3T9gu/J/WCYqqcD44dPkrU+8eYvvk/gQducsqna4HFiAg==", + "dependencies": { + "enzyme-adapter-utils": "^1.13.1", + "enzyme-shallow-equal": "^1.0.4", + "has": "^1.0.3", + "object.assign": "^4.1.0", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^18.0.0", + "react-reconciler": "^0.29.0", + "react-test-renderer": "^18.0.0", + "semver": "^5.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "enzyme": "^3.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@mnajdova/enzyme-adapter-react-18/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/@mui/base": { "version": "5.0.0-beta.7", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.7.tgz", @@ -3139,6 +3303,39 @@ } } }, + "node_modules/@mui/internal-test-utils": { + "version": "1.0.0-dev.20240529-082515-213b5e33ab", + "resolved": "https://registry.npmjs.org/@mui/internal-test-utils/-/internal-test-utils-1.0.0-dev.20240529-082515-213b5e33ab.tgz", + "integrity": "sha512-4TEDRwZvsTDRpyBLSubiMh1W+UgK4YZ8kRDEp7zWeEKEugr1lOji/fE8yeaWKtJVMKwEW8cTH0QtSlRn3oDzaw==", + "dependencies": { + "@babel/plugin-transform-modules-commonjs": "^7.24.6", + "@babel/preset-typescript": "^7.24.6", + "@babel/register": "^7.24.6", + "@babel/runtime": "^7.24.6", + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.11.4", + "@mnajdova/enzyme-adapter-react-18": "^0.2.0", + "@testing-library/dom": "^10.1.0", + "@testing-library/react": "^15.0.7", + "chai": "^4.4.1", + "chai-dom": "^1.12.0", + "dom-accessibility-api": "^0.6.3", + "enzyme": "^3.11.0", + "format-util": "^1.0.5", + "fs-extra": "^11.2.0", + "jsdom": "^24.0.0", + "lodash": "^4.17.21", + "mocha": "^10.4.0", + "playwright": "^1.44.0", + "prop-types": "^15.8.1", + "react-test-renderer": "^18.2.0", + "sinon": "^16.1.3" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/@mui/lab": { "version": "5.0.0-alpha.136", "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.136.tgz", @@ -3408,14 +3605,15 @@ } }, "node_modules/@mui/x-data-grid": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.3.0.tgz", - "integrity": "sha512-IIDS6Yvxe+1eRj65q8cgnJg5yF2aIJYuHrY00W/UaFyjxwj3xSzqg3bdEfbjE2gHGS7lEJJbXSenPNGybzW99A==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.6.0.tgz", + "integrity": "sha512-sMtnORdTRkLy+oQ3vkWaQ5c7yUdd6ovdgy3UGsn+QNqbA2hgLafFweH53nKMtNS+aULrJ1p5kCwK+DhqIYVrsA==", "dependencies": { - "@babel/runtime": "^7.24.0", - "@mui/system": "^5.15.14", + "@babel/runtime": "^7.24.6", + "@mui/internal-test-utils": "1.0.0-dev.20240529-082515-213b5e33ab", + "@mui/system": "^5.15.15", "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", + "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^4.1.8" }, @@ -3433,9 +3631,9 @@ } }, "node_modules/@mui/x-data-grid/node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -3554,12 +3752,12 @@ } }, "node_modules/@nivo/annotations": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.85.1.tgz", - "integrity": "sha512-+YVFKMokf6MMXsztpEoOoFwG+XcEJV90xezuqJ8FmS0hgEzJ8xTeWNxPRWfrvxndMXNrau4QIRU5GrumBmiy4Q==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.86.0.tgz", + "integrity": "sha512-7D5h2FzjDhAMzN9siLXSGoy1+7ZcKFu6nSoqrc4q8e1somoIw91czsGq7lowG1g4BUoLC1ZCPNRpi7pzb13JCg==", "dependencies": { - "@nivo/colors": "0.85.1", - "@nivo/core": "0.85.1", + "@nivo/colors": "0.86.0", + "@nivo/core": "0.86.0", "@react-spring/web": "9.4.5 || ^9.7.2", "@types/prop-types": "^15.7.2", "lodash": "^4.17.21", @@ -3570,12 +3768,12 @@ } }, "node_modules/@nivo/axes": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.85.1.tgz", - "integrity": "sha512-qhqyamgH8CAdOGEiLwwnqMpPKN6bv9FmKr/75UrNcAvWbU0PZ3unZJGKNkuFzlVAI9/RVvOUvXEE0rRBqV93qg==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.86.0.tgz", + "integrity": "sha512-puIjpx9GysC4Aey15CPkXrUkLY2Tr7NqP8SCYK6W2MlCjaitkpreD392TCTog1X3LTi39snLsXPl5W9cBW+V2w==", "dependencies": { - "@nivo/core": "0.85.1", - "@nivo/scales": "0.85.1", + "@nivo/core": "0.86.0", + "@nivo/scales": "0.86.0", "@react-spring/web": "9.4.5 || ^9.7.2", "@types/d3-format": "^1.4.1", "@types/d3-time-format": "^2.3.1", @@ -3589,11 +3787,11 @@ } }, "node_modules/@nivo/colors": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.85.1.tgz", - "integrity": "sha512-61qG98cfyku0fTJTdtCTS3zBQKt88URh4FAvlQIoifvKg0607S2Gz5l7P9KJfN7xEK5tmE4bRaOMmjc4AZS2Kg==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.86.0.tgz", + "integrity": "sha512-qsUlpFe4ipGEciQMnicvxL0PeOGspQFfzqJ+lpU5eUeP/C9zLUKGiRRefXRNZMHtY/V1arqFa1P9TD8IXwXF/w==", "dependencies": { - "@nivo/core": "0.85.1", + "@nivo/core": "0.86.0", "@types/d3-color": "^3.0.0", "@types/d3-scale": "^4.0.8", "@types/d3-scale-chromatic": "^3.0.0", @@ -3609,12 +3807,12 @@ } }, "node_modules/@nivo/core": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.85.1.tgz", - "integrity": "sha512-366bc4hBicsitcinQyKGfUPpifk5W60RAjwZ4sQkY8R6OzwPMgY+eu/sfPZTNcY7rsleGg8whX0A2dBg2czWMA==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.86.0.tgz", + "integrity": "sha512-0laA4BYXbDO85wOnWmzM7iv78wCjTF9tt2kEkRIXfHS3sHWCvasL2p+BJSO9/BJNHSgaHoseTdLX78Kg+ofl0Q==", "dependencies": { - "@nivo/recompose": "0.85.0", - "@nivo/tooltip": "0.85.1", + "@nivo/recompose": "0.86.0", + "@nivo/tooltip": "0.86.0", "@react-spring/web": "9.4.5 || ^9.7.2", "@types/d3-shape": "^2.0.0", "d3-color": "^3.1.0", @@ -3636,12 +3834,12 @@ } }, "node_modules/@nivo/legends": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.85.1.tgz", - "integrity": "sha512-v2DRiUieo3/iV1Fft3i9pbGTkE5arXzmw+p1ptb4xfBBPpd0hSAHvaePXDY370G31dsh2v5LouL97u+q12li4Q==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.86.0.tgz", + "integrity": "sha512-J80tUFh3OZFz+bQ7BdiUtXbWzfQlMsKOlpsn5Ys9g8r/y8bhBagmMaHfq53SYJ7ottHyibgiOvtxfWR6OGA57g==", "dependencies": { - "@nivo/colors": "0.85.1", - "@nivo/core": "0.85.1", + "@nivo/colors": "0.86.0", + "@nivo/core": "0.86.0", "@types/d3-scale": "^4.0.8", "@types/prop-types": "^15.7.2", "d3-scale": "^4.0.2", @@ -3652,18 +3850,18 @@ } }, "node_modules/@nivo/line": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.85.1.tgz", - "integrity": "sha512-BLswEMiBiFxpHaRoiKp7d3S4P3gzj0OYVBojFEEG+g19lmIEeTTc7aZsXz2pTz/NdzM6fwZqTD3llIhl6LfXFg==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.86.0.tgz", + "integrity": "sha512-y6tmhuXo2gU5AKSMsbGgHzrwRNkKaoxetmVOat25Be/e7eIDyDYJTfFDJJ2U9q1y/fcOmYUEUEV5jAIG4d2abA==", "dependencies": { - "@nivo/annotations": "0.85.1", - "@nivo/axes": "0.85.1", - "@nivo/colors": "0.85.1", - "@nivo/core": "0.85.1", - "@nivo/legends": "0.85.1", - "@nivo/scales": "0.85.1", - "@nivo/tooltip": "0.85.1", - "@nivo/voronoi": "0.85.1", + "@nivo/annotations": "0.86.0", + "@nivo/axes": "0.86.0", + "@nivo/colors": "0.86.0", + "@nivo/core": "0.86.0", + "@nivo/legends": "0.86.0", + "@nivo/scales": "0.86.0", + "@nivo/tooltip": "0.86.0", + "@nivo/voronoi": "0.86.0", "@react-spring/web": "9.4.5 || ^9.7.2", "d3-shape": "^1.3.5", "prop-types": "^15.7.2" @@ -3673,9 +3871,9 @@ } }, "node_modules/@nivo/recompose": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.85.0.tgz", - "integrity": "sha512-UptKwVJ9mlGQKn4a/JiORWbZgo6hT+qEpKBKIs9BUHRIW0a4T0BIE2PA+uDMPpNxzNFgOCu/y8iM5Rhs6QmrmA==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.86.0.tgz", + "integrity": "sha512-fS0FKcsAK6auMc1oFszSpPw+uaXSQcuc1bDOjcXtb2FwidnG3hzQkLdnK42ksi/bUZyScpHu8+c0Df+CmDNXrw==", "dependencies": { "@types/prop-types": "^15.7.2", "@types/react-lifecycles-compat": "^3.0.1", @@ -3687,9 +3885,9 @@ } }, "node_modules/@nivo/scales": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.85.1.tgz", - "integrity": "sha512-zObimCMjbbioMpQtVSGmr52OTn+BVJZsyhKHFx7CK57RA+OW/9lGnvqzc0rnFxl8WBqvHk7wReE5UI8xva/6Zw==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.86.0.tgz", + "integrity": "sha512-zw+/BVUo7PFivZVoSAgsocQZ4BtEzjNAijfZT03zVs1i+TJ1ViMtoHafAXkDtIE7+ie0IqeVVhjjTv3RfsFxrQ==", "dependencies": { "@types/d3-scale": "^4.0.8", "@types/d3-time": "^1.1.1", @@ -3706,11 +3904,11 @@ "integrity": "sha512-or9DiDnYI1h38J9hxKEsw513+KVuFbEVhl7qdxcaudoiqWWepapUen+2vAriFGexr6W5+P4l9+HJrB39GG+oRg==" }, "node_modules/@nivo/tooltip": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.85.1.tgz", - "integrity": "sha512-lX0/MuDI9HvGzYxAtE3mnriYEgFHBWf7d5BMqUifJZIyg82XkI9g3z6vwAwPKRJ52rON9Yhik42+gwFMFj3BrA==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.86.0.tgz", + "integrity": "sha512-esaON+SltO+8jhyvhiInTrhswms9zCO9e5xIycHmhPbgijV+WdFp4WDtT9l6Lb6kSS00AYzHq7P0p6lyMg4v9g==", "dependencies": { - "@nivo/core": "0.85.1", + "@nivo/core": "0.86.0", "@react-spring/web": "9.4.5 || ^9.7.2" }, "peerDependencies": { @@ -3718,11 +3916,11 @@ } }, "node_modules/@nivo/voronoi": { - "version": "0.85.1", - "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.85.1.tgz", - "integrity": "sha512-HJuc1Lhc7RhJyZCnn2eB1nqX6tsczUY4Z1YY3rl1Gy5HfW1vpoJZHQtWzelnvVcpj3qTrwI9QGLmDYE12HAeOQ==", + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.86.0.tgz", + "integrity": "sha512-BPjHpBu7KQXndNePNHWv37AXD1VwIsCZLhyUGuWPUkNidUMG8il0UgK2ej46OKbOeyQEhWjtMEtUG3M1uQk3Ng==", "dependencies": { - "@nivo/core": "0.85.1", + "@nivo/core": "0.86.0", "@types/d3-delaunay": "^5.3.0", "@types/d3-scale": "^4.0.8", "d3-delaunay": "^5.3.0", @@ -3899,7 +4097,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, "dependencies": { "type-detect": "4.0.8" } @@ -3908,11 +4105,172 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, + "node_modules/@testing-library/dom": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.1.0.tgz", + "integrity": "sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/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==" + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "15.0.7", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-15.0.7.tgz", + "integrity": "sha512-cg0RvEdD1TIhhkm1IeYMQxrzy0MtUNfa3minv4MjbgcYzJAZ7yD0i0lwoPOTPr+INtiXFezt2o8xMSnyHhEn2Q==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^10.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^18.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -3945,6 +4303,11 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, "node_modules/@types/babel__core": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", @@ -4189,7 +4552,6 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, "dependencies": { "@types/react": "*" } @@ -4789,6 +5151,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4851,6 +5224,14 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -4870,7 +5251,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" } @@ -4890,7 +5270,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4908,14 +5287,20 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "is-array-buffer": "^3.0.4" @@ -4955,6 +5340,43 @@ "node": ">=8" } }, + "node_modules/array.prototype.filter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.4.tgz", + "integrity": "sha512-r+mCJ7zXgXElgR4IRC+fkvNCeoaavWBs6EdCso5Tbcf+iEMKzBU/His60lt34WEZ9vlb8wDkZvQGcVI5GwkfoQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-array-method-boxes-properly": "^1.0.0", + "es-object-atoms": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.find": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.3.tgz", + "integrity": "sha512-fO/ORdOELvjbbeIfZfzrXFMhYHGofRGqd+am9zm3tZ4GlJINj/pA2eITyfd65Vg6+ZbHd/Cys7stpoRSWtQFdA==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlast": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", @@ -4978,7 +5400,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -5039,7 +5460,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.5", @@ -5068,6 +5488,14 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, "node_modules/asynciterator.prototype": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", @@ -5077,11 +5505,15 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -5326,8 +5758,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base-ex": { "version": "0.8.1", @@ -5369,6 +5800,17 @@ "node": "*" } }, + "node_modules/binary-extensions": { + "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/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", @@ -5412,7 +5854,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -5420,6 +5861,11 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, "node_modules/browserslist": { "version": "4.21.9", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", @@ -5463,8 +5909,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/bufferutil": { "version": "4.0.7", @@ -5536,7 +5981,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -5587,6 +6031,34 @@ } ] }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-dom": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/chai-dom/-/chai-dom-1.12.0.tgz", + "integrity": "sha512-pLP8h6IBR8z1AdeQ+EMcJ7dXPdsax/1Q7gdGZjsnAmSBl3/gItQUYSCo32br1qOy4SlcBjvqId7ilAf3uJ2K1w==", + "engines": { + "node": ">= 0.12.0" + }, + "peerDependencies": { + "chai": ">= 3" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5617,6 +6089,90 @@ "node": ">=10" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "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/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -5665,7 +6221,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -5718,6 +6273,17 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -5732,6 +6298,11 @@ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5940,6 +6511,22 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -6058,6 +6645,97 @@ "d3-time": "1 - 2" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -6089,12 +6767,39 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6114,7 +6819,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -6131,7 +6835,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -6149,6 +6852,22 @@ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -6188,6 +6907,11 @@ "node": ">=8" } }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6200,6 +6924,11 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -6280,8 +7009,7 @@ "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==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -6327,6 +7055,98 @@ "node": ">=4" } }, + "node_modules/enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dependencies": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/enzyme-adapter-utils": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.2.tgz", + "integrity": "sha512-1ZC++RlsYRaiOWE5NRaF5OgsMt7F5rn/VuaJIgc7eW/fmgg8eS1/Ut7EugSPPi7VMdWMLcymRnMF+mJUJ4B8KA==", + "dependencies": { + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.6", + "hasown": "^2.0.0", + "object.assign": "^4.1.5", + "object.fromentries": "^2.0.7", + "prop-types": "^15.8.1", + "semver": "^6.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" + } + }, + "node_modules/enzyme-adapter-utils/node_modules/airbnb-prop-types": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", + "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", + "deprecated": "This package has been renamed to 'prop-types-tools'", + "dependencies": { + "array.prototype.find": "^2.1.1", + "function.prototype.name": "^1.1.2", + "is-regex": "^1.1.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.13.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0-alpha" + } + }, + "node_modules/enzyme-adapter-utils/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/enzyme-shallow-equal": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.7.tgz", + "integrity": "sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==", + "dependencies": { + "hasown": "^2.0.0", + "object-is": "^1.1.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -6336,17 +7156,20 @@ } }, "node_modules/es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", - "dev": true, + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", @@ -6357,10 +7180,11 @@ "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.1", + "hasown": "^2.0.2", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.3", @@ -6371,17 +7195,17 @@ "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", + "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -6390,11 +7214,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -6406,7 +7234,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6443,11 +7270,21 @@ "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4", "has-tostringtag": "^1.0.2", @@ -6461,7 +7298,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" } @@ -6470,7 +7306,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -7418,7 +8253,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7451,7 +8285,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -7463,6 +8296,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -7486,16 +8327,58 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "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==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, "node_modules/function-bind": { "version": "1.1.2", @@ -7509,7 +8392,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -7527,7 +8409,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7544,16 +8425,22 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -7593,7 +8480,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "es-errors": "^1.3.0", @@ -7668,7 +8554,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -7703,7 +8588,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -7714,8 +8598,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -7727,7 +8610,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -7739,7 +8621,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7756,7 +8637,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -7768,7 +8648,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7780,7 +8659,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7792,7 +8670,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -7814,6 +8691,14 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -7827,6 +8712,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/html-element-map": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.3.1.tgz", + "integrity": "sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==", + "dependencies": { + "array.prototype.filter": "^1.0.0", + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7841,6 +8749,48 @@ "void-elements": "3.1.0" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7888,6 +8838,17 @@ "cross-fetch": "4.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -8008,7 +8969,6 @@ "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" @@ -8031,7 +8991,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.0", @@ -8059,7 +9018,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1" @@ -8095,7 +9053,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -8103,11 +9060,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8138,7 +9105,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8157,11 +9123,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8176,7 +9155,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8197,7 +9175,6 @@ "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==", - "dev": true, "engines": { "node": ">=8" } @@ -8230,7 +9207,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -8254,7 +9230,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8266,7 +9241,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -8275,7 +9249,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8295,11 +9268,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -8307,11 +9287,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8339,7 +9323,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, "dependencies": { "call-bind": "^1.0.7" }, @@ -8366,7 +9349,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8377,11 +9359,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==" + }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -8396,7 +9382,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.14" }, @@ -8412,6 +9397,17 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -8428,7 +9424,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -8455,8 +9450,7 @@ "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/isexe": { "version": "2.0.0", @@ -8468,7 +9462,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10174,7 +11167,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -10182,6 +11174,76 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", + "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.10", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.4", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.17.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -10220,6 +11282,17 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz", @@ -10235,11 +11308,15 @@ "node": ">=4.0" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10327,7 +11404,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -10349,12 +11425,111 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==" + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -10366,6 +11541,14 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -10374,6 +11557,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10441,7 +11632,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -10450,7 +11640,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -10493,6 +11682,161 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mocha": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "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" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10528,6 +11872,32 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -10539,6 +11909,26 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10583,7 +11973,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13156,6 +14545,11 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nwsapi": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13168,7 +14562,21 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13177,7 +14585,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -13186,7 +14593,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -13204,7 +14610,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -13218,7 +14623,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -13248,7 +14652,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -13265,7 +14668,6 @@ "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" } @@ -13317,7 +14719,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -13332,7 +14733,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -13347,7 +14747,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "engines": { "node": ">=6" } @@ -13380,11 +14779,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -13412,6 +14833,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -13420,6 +14846,19 @@ "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -13429,7 +14868,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -13437,11 +14875,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -13543,6 +14988,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -13560,7 +15046,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -13611,9 +15096,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -13686,15 +15171,35 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-exact": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.4.tgz", + "integrity": "sha512-vKfETKgBHRCLQwZgpl0pGPvMFxCX/06dAkz5jwNYHfrU0I8bgVhryaHA6O/KlqwtQi0IdnjAhDiZqzD+uJuVjA==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "isarray": "^2.0.5", + "object.assign": "^4.1.5", + "reflect.ownkeys": "^1.1.4" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "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==", "engines": { "node": ">=6" } @@ -13720,6 +15225,11 @@ "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==" }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13740,19 +15250,43 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -13894,6 +15428,21 @@ } } }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-resizable": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", @@ -13936,6 +15485,18 @@ "react-dom": ">=16.8" } }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-smooth-image": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-smooth-image/-/react-smooth-image-1.1.0.tgz", @@ -13947,6 +15508,24 @@ "react": "* || ^16.4.1" } }, + "node_modules/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", + "dependencies": { + "react-is": "^18.3.1", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -13975,6 +15554,17 @@ "react": ">=0.14" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -14013,6 +15603,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reflect.ownkeys": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-1.1.4.tgz", + "integrity": "sha512-iUNmtLgzudssL+qnTUosCmnq3eczlrVd1wXrgx/GhiI/8FvwrTYWtCJ9PNvWIRX+4ftupj2WUfB5mu5s9t6LnA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "globalthis": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -14049,7 +15654,6 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "define-properties": "^1.2.1", @@ -14105,7 +15709,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -14119,6 +15722,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/reselect": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", @@ -14192,6 +15800,14 @@ "node": ">=10" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14222,6 +15838,20 @@ "resolved": "https://registry.npmjs.org/robo-identities-wasm/-/robo-identities-wasm-0.1.0.tgz", "integrity": "sha512-q6+1Vgq+8d2F5k8Nqm39qwQJYe9uTC7TlR3NbBQ6k2ImBNccdAEoZgb0ikKjN59cK4MvqejlgBV1ybaLXoHbhA==" }, + "node_modules/rrweb-cssom": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz", + "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==" + }, + "node_modules/rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==", + "dependencies": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -14249,7 +15879,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "get-intrinsic": "^1.2.4", @@ -14267,7 +15896,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -14287,7 +15915,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", @@ -14305,10 +15932,21 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -14387,7 +16025,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -14404,7 +16041,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -14419,7 +16055,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, "dependencies": { "kind-of": "^6.0.2" }, @@ -14452,7 +16087,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -14478,6 +16112,50 @@ "plist": "^3.0.5" } }, + "node_modules/sinon": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -14580,7 +16258,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14611,14 +16288,14 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -14628,28 +16305,29 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14659,7 +16337,6 @@ "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" }, @@ -14689,7 +16366,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -14752,6 +16428,11 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, "node_modules/synckit": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", @@ -14948,7 +16629,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -14956,6 +16636,28 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -15085,7 +16787,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, "engines": { "node": ">=4" } @@ -15106,7 +16807,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -15120,7 +16820,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -15139,7 +16838,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -15156,10 +16854,9 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -15200,7 +16897,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -15263,6 +16959,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -15300,6 +17004,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", @@ -15340,6 +17053,17 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -15545,6 +17269,25 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -15573,7 +17316,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -15633,7 +17375,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -15654,6 +17395,11 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" + }, "node_modules/world-countries": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/world-countries/-/world-countries-5.0.0.tgz", @@ -15663,7 +17409,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15680,7 +17425,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" }, @@ -15695,7 +17439,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" }, @@ -15706,14 +17449,12 @@ "node_modules/wrap-ansi/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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -15728,6 +17469,34 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -15736,11 +17505,15 @@ "node": ">=8.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -15793,6 +17566,31 @@ "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -15806,7 +17604,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, diff --git a/frontend/package.json b/frontend/package.json index cb70fdf2..b1b370e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.6.0", + "version": "0.6.2", "description": "", "main": "index.js", "scripts": { @@ -41,7 +41,7 @@ "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^29.6.1", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "ts-node": "^10.9.2", "typescript": "^5.4.2", "webpack": "^5.89.0", @@ -57,10 +57,10 @@ "@mui/lab": "^5.0.0-alpha.136", "@mui/material": "^5.15.14", "@mui/system": "^5.15.11", - "@mui/x-data-grid": "^7.3.0", + "@mui/x-data-grid": "^7.6.0", "@mui/x-date-pickers": "^7.2.0", - "@nivo/core": "^0.85.1", - "@nivo/line": "^0.85.1", + "@nivo/core": "^0.86.0", + "@nivo/line": "^0.86.0", "base-ex": "^0.8.1", "country-flag-icons": "^1.5.11", "date-fns": "^2.30.0", diff --git a/frontend/src/basic/RobotPage/Onboarding.tsx b/frontend/src/basic/RobotPage/Onboarding.tsx index 38d54aac..1d6d2fad 100644 --- a/frontend/src/basic/RobotPage/Onboarding.tsx +++ b/frontend/src/basic/RobotPage/Onboarding.tsx @@ -178,7 +178,7 @@ const Onboarding = ({ /> - {slot?.hashId ? ( + {slot?.nickname ? ( {t('Hi! My name is')} diff --git a/frontend/src/basic/RobotPage/RobotProfile.tsx b/frontend/src/basic/RobotPage/RobotProfile.tsx index 2ac4f354..4a21bfbb 100644 --- a/frontend/src/basic/RobotPage/RobotProfile.tsx +++ b/frontend/src/basic/RobotPage/RobotProfile.tsx @@ -90,7 +90,7 @@ const RobotProfile = ({ sx={{ width: '100%' }} > - {slot?.hashId ? ( + {slot?.nickname ? (
{ const token = urlToken ?? garage.currentSlot; if (token !== undefined && token !== null && page === 'robot') { setInputToken(token); - if (window.NativeRobosats === undefined || torStatus === '"Done"') { + if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) { getGenerateRobot(token); setView('profile'); } @@ -83,7 +83,7 @@ const RobotPage = (): JSX.Element => { garage.deleteSlot(); }; - if (!(window.NativeRobosats === undefined) && !(torStatus === 'DONE' || torStatus === '"Done"')) { + if (settings.useProxy && !(window.NativeRobosats === undefined) && !(torStatus === 'ON')) { return ( { - {garage.getSlot()?.nickname !== undefined && ( + {!garage.getSlot()?.nickname && (
{ - // Why? - // const slot = garage.getSlot(); - // if (slot?.token) void federation.fetchRobot(garage, slot?.token); - }, [garage.currentSlot]); - useEffect(() => { setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]); }, [coordinatorUpdatedAt]); diff --git a/frontend/src/components/OrderDetails/index.tsx b/frontend/src/components/OrderDetails/index.tsx index 53c97055..747159fe 100644 --- a/frontend/src/components/OrderDetails/index.tsx +++ b/frontend/src/components/OrderDetails/index.tsx @@ -178,7 +178,7 @@ const OrderDetails = ({ : coordinator.info?.taker_fee ?? 0; const defaultRoutingBudget = 0.001; const btc_now = order.satoshis_now / 100000000; - const rate = order.amount > 0 ? order.amount / btc_now : Number(order.max_amount) / btc_now; + const rate = Number(order.max_amount ?? order.amount) / btc_now; if (isBuyer) { if (order.amount > 0) { @@ -207,7 +207,7 @@ const OrderDetails = ({ amount: amountString, method: order.payment_method, }); - receive = t('You receive via Lightning {{amount}} Sats (Approx)', { + receive = t('You receive {{amount}} Sats (Approx)', { amount: sats, }); } else { diff --git a/frontend/src/components/RobotAvatar/index.tsx b/frontend/src/components/RobotAvatar/index.tsx index c4992f8d..33fe8f5b 100644 --- a/frontend/src/components/RobotAvatar/index.tsx +++ b/frontend/src/components/RobotAvatar/index.tsx @@ -3,8 +3,8 @@ import SmoothImage from 'react-smooth-image'; import { Avatar, Badge, Tooltip } from '@mui/material'; import { SendReceiveIcon } from '../Icons'; import placeholder from './placeholder.json'; -import { robohash } from './RobohashGenerator'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; +import { roboidentitiesClient } from '../../services/Roboidentities/Web'; interface Props { shortAlias?: string | undefined; @@ -54,10 +54,9 @@ const RobotAvatar: React.FC = ({ const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar'; useEffect(() => { - // TODO: HANDLE ANDROID AVATARS TOO (when window.NativeRobosats !== undefined) if (hashId !== undefined) { - robohash - .generate(hashId, small ? 'small' : 'large') + roboidentitiesClient + .generateRobohash(hashId, small ? 'small' : 'large') .then((avatar) => { setAvatarSrc(avatar); }) @@ -78,9 +77,7 @@ const RobotAvatar: React.FC = ({ ); } else { setAvatarSrc( - `file:///android_asset/Web.bundle/assets/federation/avatars/${shortAlias}${ - small ? ' .small' : '' - }.webp`, + `file:///android_asset/Web.bundle/assets/federation/avatars/${shortAlias}.webp`, ); } setTimeout(() => { diff --git a/frontend/src/components/RobotInfo/index.tsx b/frontend/src/components/RobotInfo/index.tsx index b5ce895f..382a222b 100644 --- a/frontend/src/components/RobotInfo/index.tsx +++ b/frontend/src/components/RobotInfo/index.tsx @@ -95,7 +95,6 @@ const RobotInfo: React.FC = ({ coordinator, onClose, disabled }: Props) = (signedInvoice) => { console.log('Signed message:', signedInvoice); void coordinator.fetchReward(signedInvoice, garage, slot?.token).then((data) => { - console.log(data); setBadInvoice(data.bad_invoice ?? ''); setShowRewardsSpinner(false); setWithdrawn(data.successful_withdrawal); diff --git a/frontend/src/components/SettingsForm/index.tsx b/frontend/src/components/SettingsForm/index.tsx index 7d1f149d..22eaef92 100644 --- a/frontend/src/components/SettingsForm/index.tsx +++ b/frontend/src/components/SettingsForm/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { type UseAppStoreType, AppContext } from '../../contexts/AppContext'; import { @@ -28,17 +28,19 @@ import { QrCode, } from '@mui/icons-material'; import { systemClient } from '../../services/System'; +import { TorIcon } from '../Icons'; import SwapCalls from '@mui/icons-material/SwapCalls'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; +import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext'; interface SettingsFormProps { dense?: boolean; } const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { - const { fav, setFav, origin, hostUrl, settings, setSettings } = - useContext(AppContext); + const { fav, setFav, settings, setSettings } = useContext(AppContext); const { federation } = useContext(FederationContext); + const { garage } = useContext(GarageContext); const theme = useTheme(); const { t } = useTranslation(); const fontSizes = [ @@ -226,7 +228,6 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { value={settings.network} onChange={(e, network) => { setSettings({ ...settings, network }); - void federation.updateUrls(origin, { ...settings, network }, hostUrl); systemClient.setItem('settings_network', network); }} > @@ -238,6 +239,29 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { + + {window.NativeRobosats !== undefined && ( + + + + + { + setSettings({ ...settings, useProxy }); + systemClient.setItem('settings_use_proxy', String(useProxy)); + }} + > + + {t('Build-in')} + + + {t('Disabled')} + + + + )} diff --git a/frontend/src/components/TorConnection.tsx b/frontend/src/components/TorConnection.tsx index 77e33b88..b9d068be 100644 --- a/frontend/src/components/TorConnection.tsx +++ b/frontend/src/components/TorConnection.tsx @@ -55,14 +55,14 @@ const TorIndicator = ({ }; const TorConnectionBadge = (): JSX.Element => { - const { torStatus } = useContext(AppContext); + const { torStatus, settings } = useContext(AppContext); const { t } = useTranslation(); - if (window?.NativeRobosats == null) { + if (window?.NativeRobosats == null || !settings.useProxy) { return <>; } - if (torStatus === 'NOTINIT') { + if (torStatus === 'OFF' || torStatus === 'STOPPING') { return ( { title={t('Connecting to TOR network')} /> ); - } else if (torStatus === '"Done"' || torStatus === 'DONE') { + } else if (torStatus === 'ON') { return ; } else { return ( diff --git a/frontend/src/components/TorConnection/index.tsx b/frontend/src/components/TorConnection/index.tsx index deffc0da..4484c213 100644 --- a/frontend/src/components/TorConnection/index.tsx +++ b/frontend/src/components/TorConnection/index.tsx @@ -62,7 +62,7 @@ const TorConnectionBadge = (): JSX.Element => { return <>; } - if (torStatus === 'NOTINIT') { + if (torStatus === 'OFF' || torStatus === 'STOPING') { return ( { title={t('Connecting to TOR network')} /> ); - } else if (torStatus === '"Done"' || torStatus === 'DONE') { + } else if (torStatus === 'ON') { return ; } else { return ( diff --git a/frontend/src/components/TradeBox/Prompts/Successful.tsx b/frontend/src/components/TradeBox/Prompts/Successful.tsx index f0fd3be3..5175e878 100644 --- a/frontend/src/components/TradeBox/Prompts/Successful.tsx +++ b/frontend/src/components/TradeBox/Prompts/Successful.tsx @@ -222,6 +222,7 @@ export const SuccessfulPrompt = ({ takerSummary={order.taker_summary} platformSummary={order.platform_summary} orderId={order.id} + coordinatorLongAlias={federation.getCoordinator(order.shortAlias)?.longAlias} /> ) : ( diff --git a/frontend/src/components/TradeBox/TradeSummary.tsx b/frontend/src/components/TradeBox/TradeSummary.tsx index cef4875e..2c691cce 100644 --- a/frontend/src/components/TradeBox/TradeSummary.tsx +++ b/frontend/src/components/TradeBox/TradeSummary.tsx @@ -43,6 +43,7 @@ interface Props { makerHashId: string; takerHashId: string; currencyCode: string; + coordinatorLongAlias: string; makerSummary: TradeRobotSummary; takerSummary: TradeRobotSummary; platformSummary: TradeCoordinatorSummary; @@ -54,6 +55,7 @@ const TradeSummary = ({ makerHashId, takerHashId, currencyCode, + coordinatorLongAlias, makerSummary, takerSummary, platformSummary, @@ -72,6 +74,7 @@ const TradeSummary = ({ const onClickExport = function (): void { const summary = { + coordinator: coordinatorLongAlias, order_id: orderId, currency: currencyCode, maker: makerSummary, diff --git a/frontend/src/contexts/AppContext.tsx b/frontend/src/contexts/AppContext.tsx index b2a79260..45b4a84c 100644 --- a/frontend/src/contexts/AppContext.tsx +++ b/frontend/src/contexts/AppContext.tsx @@ -37,7 +37,7 @@ export interface SlideDirection { out: 'left' | 'right' | undefined; } -export type TorStatus = 'NOTINIT' | 'STARTING' | '"Done"' | 'DONE'; +export type TorStatus = 'ON' | 'STARTING' | 'STOPPING' | 'OFF'; export const isNativeRoboSats = !(window.NativeRobosats === undefined); @@ -155,8 +155,8 @@ export interface UseAppStoreType { export const initialAppContext: UseAppStoreType = { theme: undefined, - torStatus: 'NOTINIT', - settings: getSettings(), + torStatus: 'STARTING', + settings: new Settings(), setSettings: () => {}, page: entryPage, setPage: () => {}, @@ -225,7 +225,7 @@ export const AppContextProvider = ({ children }: AppContextProviderProps): JSX.E () => { setTorStatus(event?.detail); }, - event?.detail === '"Done"' ? 5000 : 0, + event?.detail === 'ON' ? 5000 : 0, ); }); }, []); diff --git a/frontend/src/contexts/FederationContext.tsx b/frontend/src/contexts/FederationContext.tsx index a1f3ac8e..b7998111 100644 --- a/frontend/src/contexts/FederationContext.tsx +++ b/frontend/src/contexts/FederationContext.tsx @@ -9,12 +9,13 @@ import React, { type ReactNode, } from 'react'; -import { type Order, Federation } from '../models'; +import { type Order, Federation, Settings } from '../models'; import { federationLottery } from '../utils'; import { AppContext, type UseAppStoreType } from './AppContext'; import { GarageContext, type UseGarageStoreType } from './GarageContext'; +import NativeRobosats from '../services/Native'; // Refresh delays (ms) according to Order status const defaultDelay = 5000; @@ -61,7 +62,7 @@ export interface UseFederationStoreType { } export const initialFederationContext: UseFederationStoreType = { - federation: new Federation(), + federation: new Federation('onion', new Settings(), ''), sortedCoordinators: [], setDelay: () => {}, currentOrderId: { id: null, shortAlias: null }, @@ -79,7 +80,7 @@ export const FederationContextProvider = ({ const { settings, page, origin, hostUrl, open, torStatus } = useContext(AppContext); const { setMaker, garage, setBadOrder } = useContext(GarageContext); - const [federation, setFederation] = useState(initialFederationContext.federation); + const [federation] = useState(new Federation(origin, settings, hostUrl)); const sortedCoordinators = useMemo(() => federationLottery(federation), []); const [coordinatorUpdatedAt, setCoordinatorUpdatedAt] = useState( new Date().toISOString(), @@ -101,20 +102,23 @@ export const FederationContextProvider = ({ setMaker((maker) => { return { ...maker, coordinator: sortedCoordinators[0] }; }); // default MakerForm coordinator is decided via sorted lottery + federation.registerHook('onFederationUpdate', () => { + setFederationUpdatedAt(new Date().toISOString()); + }); + federation.registerHook('onCoordinatorUpdate', () => { + setCoordinatorUpdatedAt(new Date().toISOString()); + }); }, []); useEffect(() => { - // On bitcoin network change we reset book, limits and federation info and fetch everything again - const newFed = initialFederationContext.federation; - newFed.registerHook('onFederationUpdate', () => { - setFederationUpdatedAt(new Date().toISOString()); - }); - newFed.registerHook('onCoordinatorUpdate', () => { - setCoordinatorUpdatedAt(new Date().toISOString()); - }); - void newFed.start(origin, settings, hostUrl); - setFederation(newFed); - }, [settings.network, torStatus]); + if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) { + void federation.updateUrl(origin, settings, hostUrl); + void federation.update(); + + const token = garage.getSlot()?.getRobot()?.token; + if (token) void federation.fetchRobot(garage, token); + } + }, [settings.network, settings.useProxy, torStatus]); const onOrderReceived = (order: Order): void => { let newDelay = defaultDelay; @@ -176,15 +180,6 @@ export const FederationContextProvider = ({ if (page === 'offers') void federation.updateBook(); }, [page]); - // use effects to fetchRobots on app start and network change - useEffect(() => { - const slot = garage.getSlot(); - const robot = slot?.getRobot(); - - if (robot && garage.currentSlot && slot?.token && robot.encPrivKey && robot.pubKey) { - void federation.fetchRobot(garage, slot.token); - } - }, [settings.network]); // use effects to fetchRobots on Profile open useEffect(() => { const slot = garage.getSlot(); diff --git a/frontend/src/models/Coordinator.model.ts b/frontend/src/models/Coordinator.model.ts index cd902e16..c07e771b 100644 --- a/frontend/src/models/Coordinator.model.ts +++ b/frontend/src/models/Coordinator.model.ts @@ -6,11 +6,11 @@ import { type Order, type Garage, } from '.'; +import { roboidentitiesClient } from '../services/Roboidentities/Web'; import { apiClient } from '../services/api'; import { validateTokenEntropy } from '../utils'; import { compareUpdateLimit } from './Limit.model'; import { defaultOrder } from './Order.model'; -import { robohash } from '../components/RobotAvatar/RobohashGenerator'; export interface Contact { nostr?: string | undefined; @@ -97,7 +97,7 @@ function calculateSizeLimit(inputDate: Date): number { } export class Coordinator { - constructor(value: any) { + constructor(value: any, origin: Origin, settings: Settings, hostUrl: string) { const established = new Date(value.established); this.longAlias = value.longAlias; this.shortAlias = value.shortAlias; @@ -115,6 +115,8 @@ export class Coordinator { this.testnetNodesPubkeys = value.testnetNodesPubkeys; this.url = ''; this.basePath = ''; + + this.updateUrl(origin, settings, hostUrl); } // These properties are loaded from federation.json @@ -143,24 +145,9 @@ export class Coordinator { public loadingInfo: boolean = false; public limits: LimitList = {}; public loadingLimits: boolean = false; - public loadingRobot: boolean = true; + public loadingRobot: string | null; - start = async ( - origin: Origin, - settings: Settings, - hostUrl: string, - onUpdate: (shortAlias: string) => void = () => {}, - ): Promise => { - if (this.enabled !== true) return; - void this.updateUrl(settings, origin, hostUrl, onUpdate); - }; - - updateUrl = async ( - settings: Settings, - origin: Origin, - hostUrl: string, - onUpdate: (shortAlias: string) => void = () => {}, - ): Promise => { + updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => { if (settings.selfhostedClient && this.shortAlias !== 'local') { this.url = hostUrl; this.basePath = `/${settings.network}/${this.shortAlias}`; @@ -168,9 +155,6 @@ export class Coordinator { this.url = String(this[settings.network][origin]); this.basePath = ''; } - void this.update(() => { - onUpdate(this.shortAlias); - }); }; update = async (onUpdate: (shortAlias: string) => void = () => {}): Promise => { @@ -191,7 +175,7 @@ export class Coordinator { generateAllMakerAvatars = async (data: [PublicOrder]): Promise => { for (const order of data) { - void robohash.generate(order.maker_hash_id, 'small'); + roboidentitiesClient.generateRobohash(order.maker_hash_id, 'small'); } }; @@ -201,6 +185,7 @@ export class Coordinator { if (this.loadingBook) return; this.loadingBook = true; + this.book = []; apiClient .get(this.url, `${this.basePath}/api/book/`) @@ -313,7 +298,7 @@ export class Coordinator { }; fetchRobot = async (garage: Garage, token: string): Promise => { - if (!this.enabled || !token) return null; + if (!this.enabled || !token || this.loadingRobot === token) return null; const robot = garage?.getSlot(token)?.getRobot() ?? null; const authHeaders = robot?.getAuthHeaders(); @@ -324,6 +309,8 @@ export class Coordinator { if (!hasEnoughEntropy) return null; + this.loadingRobot = token; + garage.updateRobot(token, this.shortAlias, { loading: true }); const newAttributes = await apiClient @@ -346,7 +333,8 @@ export class Coordinator { }) .catch((e) => { console.log(e); - }); + }) + .finally(() => (this.loadingRobot = null)); garage.updateRobot(token, this.shortAlias, { ...newAttributes, @@ -370,7 +358,6 @@ export class Coordinator { return await apiClient .get(this.url, `${this.basePath}/api/order/?order_id=${orderId}`, authHeaders) .then((data) => { - console.log('data', data); const order: Order = { ...defaultOrder, ...data, diff --git a/frontend/src/models/Federation.model.ts b/frontend/src/models/Federation.model.ts index 4b26623c..2d31e6ce 100644 --- a/frontend/src/models/Federation.model.ts +++ b/frontend/src/models/Federation.model.ts @@ -14,14 +14,14 @@ import { updateExchangeInfo } from './Exchange.model'; type FederationHooks = 'onCoordinatorUpdate' | 'onFederationUpdate'; export class Federation { - constructor() { + constructor(origin: Origin, settings: Settings, hostUrl: string) { this.coordinators = Object.entries(defaultFederation).reduce( (acc: Record, [key, value]: [string, any]) => { if (getHost() !== '127.0.0.1:8000' && key === 'local') { // Do not add `Local Dev` unless it is running on localhost return acc; } else { - acc[key] = new Coordinator(value); + acc[key] = new Coordinator(value, origin, settings, hostUrl); return acc; } }, @@ -36,7 +36,16 @@ export class Federation { onCoordinatorUpdate: [], onFederationUpdate: [], }; + this.loading = true; + this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; + + const host = getHost(); + const url = `${window.location.protocol}//${host}`; + const tesnetHost = Object.values(this.coordinators).find((coor) => { + return Object.values(coor.testnet).includes(url); + }); + if (tesnetHost) settings.network = 'testnet'; } public coordinators: Record; @@ -69,38 +78,10 @@ export class Federation { this.triggerHook('onFederationUpdate'); }; - // Setup - start = async (origin: Origin, settings: Settings, hostUrl: string): Promise => { - const onCoordinatorStarted = (): void => { - this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1; - this.onCoordinatorSaved(); - }; - - this.loading = true; - this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; - - const host = getHost(); - const url = `${window.location.protocol}//${host}`; - const tesnetHost = Object.values(this.coordinators).find((coor) => { - return Object.values(coor.testnet).includes(url); - }); - if (tesnetHost) settings.network = 'testnet'; - + updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise => { for (const coor of Object.values(this.coordinators)) { - if (coor.enabled) { - await coor.start(origin, settings, hostUrl, onCoordinatorStarted); - } + coor.updateUrl(origin, settings, hostUrl); } - this.updateEnabledCoordinators(); - }; - - // On Testnet/Mainnet change - updateUrls = async (origin: Origin, settings: Settings, hostUrl: string): Promise => { - this.loading = true; - for (const coor of Object.values(this.coordinators)) { - await coor.updateUrl(settings, origin, hostUrl); - } - this.loading = false; }; update = async (): Promise => { @@ -115,9 +96,12 @@ export class Federation { lifetime_volume: 0, version: { major: 0, minor: 0, patch: 0 }, }; + this.exchange.onlineCoordinators = 0; this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; + this.updateEnabledCoordinators(); for (const coor of Object.values(this.coordinators)) { - await coor.update(() => { + coor.update(() => { + this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1; this.onCoordinatorSaved(); }); } @@ -125,10 +109,11 @@ export class Federation { updateBook = async (): Promise => { this.loading = true; + this.book = []; this.triggerHook('onCoordinatorUpdate'); this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; for (const coor of Object.values(this.coordinators)) { - await coor.updateBook(() => { + coor.updateBook(() => { this.onCoordinatorSaved(); }); } diff --git a/frontend/src/models/Garage.model.ts b/frontend/src/models/Garage.model.ts index a6258cd1..47204b91 100644 --- a/frontend/src/models/Garage.model.ts +++ b/frontend/src/models/Garage.model.ts @@ -59,7 +59,9 @@ class Garage { const rawSlots = JSON.parse(slotsDump); Object.values(rawSlots).forEach((rawSlot: Record) => { if (rawSlot?.token) { - this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}); + this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}, () => + this.triggerHook('onRobotUpdate'), + ); Object.keys(rawSlot.robots).forEach((shortAlias) => { const rawRobot = rawSlot.robots[shortAlias]; @@ -113,9 +115,10 @@ class Garage { if (!token || !shortAliases) return; if (this.getSlot(token) === null) { - this.slots[token] = new Slot(token, shortAliases, attributes); + this.slots[token] = new Slot(token, shortAliases, attributes, () => + this.triggerHook('onRobotUpdate'), + ); this.save(); - this.triggerHook('onRobotUpdate'); } }; diff --git a/frontend/src/models/Settings.model.ts b/frontend/src/models/Settings.model.ts index 203d979f..cbb0ed7a 100644 --- a/frontend/src/models/Settings.model.ts +++ b/frontend/src/models/Settings.model.ts @@ -1,5 +1,6 @@ import i18n from '../i18n/Web'; import { systemClient } from '../services/System'; +import { apiClient } from '../services/api'; import { getHost } from '../utils'; export type Language = @@ -42,8 +43,13 @@ class BaseSettings { : i18n.resolvedLanguage.substring(0, 2); const networkCookie = systemClient.getItem('settings_network'); - this.network = networkCookie !== '' ? networkCookie : 'mainnet'; + this.network = networkCookie && networkCookie !== '' ? networkCookie : 'mainnet'; this.host = getHost(); + + const useProxy = systemClient.getItem('settings_use_proxy'); + this.useProxy = window.NativeRobosats !== undefined && useProxy !== 'false'; + + apiClient.useProxy = this.useProxy; } public frontend: 'basic' | 'pro' = 'basic'; @@ -56,6 +62,7 @@ class BaseSettings { public host?: string; public unsafeClient: boolean = false; public selfhostedClient: boolean = false; + public useProxy: boolean; } export default BaseSettings; diff --git a/frontend/src/models/Slot.model.ts b/frontend/src/models/Slot.model.ts index 864820ed..76909bf0 100644 --- a/frontend/src/models/Slot.model.ts +++ b/frontend/src/models/Slot.model.ts @@ -1,17 +1,24 @@ import { sha256 } from 'js-sha256'; import { Robot, type Order } from '.'; -import { robohash } from '../components/RobotAvatar/RobohashGenerator'; -import { generate_roboname } from 'robo-identities-wasm'; +import { roboidentitiesClient } from '../services/Roboidentities/Web'; class Slot { - constructor(token: string, shortAliases: string[], robotAttributes: Record) { + constructor( + token: string, + shortAliases: string[], + robotAttributes: Record, + onRobotUpdate: () => void, + ) { this.token = token; this.hashId = sha256(sha256(this.token)); - this.nickname = generate_roboname(this.hashId); - // trigger RoboHash avatar generation in webworker and store in RoboHash class cache. - void robohash.generate(this.hashId, 'small'); - void robohash.generate(this.hashId, 'large'); + this.nickname = null; + roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => { + this.nickname = nickname; + onRobotUpdate(); + }); + roboidentitiesClient.generateRobohash(this.hashId, 'small'); + roboidentitiesClient.generateRobohash(this.hashId, 'large'); this.robots = shortAliases.reduce((acc: Record, shortAlias: string) => { acc[shortAlias] = new Robot(robotAttributes); @@ -22,6 +29,7 @@ class Slot { this.activeShortAlias = null; this.lastShortAlias = null; this.copiedToken = false; + onRobotUpdate(); } token: string | null; diff --git a/frontend/src/services/Native/index.d.ts b/frontend/src/services/Native/index.d.ts index e93edd55..680c8427 100644 --- a/frontend/src/services/Native/index.d.ts +++ b/frontend/src/services/Native/index.d.ts @@ -15,7 +15,7 @@ export interface ReactNativeWebView { export interface NativeWebViewMessageHttp { id?: number; category: 'http'; - type: 'post' | 'get' | 'put' | 'delete' | 'xhr'; + type: 'post' | 'get' | 'put' | 'delete'; path: string; baseUrl: string; headers?: object; @@ -30,7 +30,19 @@ export interface NativeWebViewMessageSystem { detail?: string; } -export declare type NativeWebViewMessage = NativeWebViewMessageHttp | NativeWebViewMessageSystem; +export interface NativeWebViewMessageRoboidentities { + id?: number; + category: 'roboidentities'; + type: 'roboname' | 'robohash'; + string?: string; + size?: string; +} + +export declare type NativeWebViewMessage = + | NativeWebViewMessageHttp + | NativeWebViewMessageSystem + | NativeWebViewMessageRoboidentities + | NA; export interface NativeRobosatsPromise { resolve: (value: object | PromiseLike) => void; diff --git a/frontend/src/services/Roboidentities/Native.ts b/frontend/src/services/Roboidentities/Native.ts new file mode 100644 index 00000000..250b9a81 --- /dev/null +++ b/frontend/src/services/Roboidentities/Native.ts @@ -0,0 +1,4 @@ +import RoboidentitiesClientNativeClient from './RoboidentitiesNativeClient'; +import { RoboidentitiesClient } from './type'; + +export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientNativeClient(); diff --git a/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts b/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts new file mode 100644 index 00000000..85b55b8b --- /dev/null +++ b/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts @@ -0,0 +1,42 @@ +import { type RoboidentitiesClient } from '../type'; + +class RoboidentitiesNativeClient implements RoboidentitiesClient { + private robonames: Record = {}; + private robohashes: Record = {}; + + public generateRoboname: (initialString: string) => Promise = async (initialString) => { + if (this.robonames[initialString]) { + return this.robonames[initialString]; + } else { + const response = await window.NativeRobosats?.postMessage({ + category: 'roboidentities', + type: 'roboname', + detail: initialString, + }); + const result = response ? Object.values(response)[0] : ''; + this.robonames[initialString] = result; + return result; + } + }; + + public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise = + async (initialString, size) => { + const key = `${initialString};${size === 'small' ? 80 : 256}`; + + if (this.robohashes[key]) { + return this.robohashes[key]; + } else { + const response = await window.NativeRobosats?.postMessage({ + category: 'roboidentities', + type: 'robohash', + detail: key, + }); + const result = response ? Object.values(response)[0] : ''; + const image = `data:image/png;base64,${result}`; + this.robohashes[key] = image; + return image; + } + }; +} + +export default RoboidentitiesNativeClient; diff --git a/frontend/src/components/RobotAvatar/RobohashGenerator.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts similarity index 98% rename from frontend/src/components/RobotAvatar/RobohashGenerator.ts rename to frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts index 038cdf92..f00075e0 100644 --- a/frontend/src/components/RobotAvatar/RobohashGenerator.ts +++ b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts @@ -81,7 +81,7 @@ class RoboGenerator { hash, size, ) => { - const cacheKey = `${size}px;${hash}`; + const cacheKey = `${hash};${size}`; if (this.assetsCache[cacheKey]) { return this.assetsCache[cacheKey]; } else { diff --git a/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts new file mode 100644 index 00000000..20e2e137 --- /dev/null +++ b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts @@ -0,0 +1,18 @@ +import { type RoboidentitiesClient } from '../type'; +import { generate_roboname } from 'robo-identities-wasm'; +import { robohash } from './RobohashGenerator'; + +class RoboidentitiesClientWebClient implements RoboidentitiesClient { + public generateRoboname: (initialString: string) => Promise = async (initialString) => { + return new Promise(async (resolve, _reject) => { + resolve(generate_roboname(initialString)); + }); + }; + + public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise = + async (initialString, size) => { + return robohash.generate(initialString, size); + }; +} + +export default RoboidentitiesClientWebClient; diff --git a/frontend/src/components/RobotAvatar/robohash.worker.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/robohash.worker.ts similarity index 100% rename from frontend/src/components/RobotAvatar/robohash.worker.ts rename to frontend/src/services/Roboidentities/RoboidentitiesWebClient/robohash.worker.ts diff --git a/frontend/src/services/Roboidentities/Web.ts b/frontend/src/services/Roboidentities/Web.ts new file mode 100644 index 00000000..8730491c --- /dev/null +++ b/frontend/src/services/Roboidentities/Web.ts @@ -0,0 +1,4 @@ +import RoboidentitiesClientWebClient from './RoboidentitiesWebClient'; +import { RoboidentitiesClient } from './type'; + +export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientWebClient(); diff --git a/frontend/src/services/Roboidentities/type.ts b/frontend/src/services/Roboidentities/type.ts new file mode 100644 index 00000000..4a54ea99 --- /dev/null +++ b/frontend/src/services/Roboidentities/type.ts @@ -0,0 +1,4 @@ +export interface RoboidentitiesClient { + generateRoboname: (initialString: string) => Promise; + generateRobohash: (initialString: string, size: 'small' | 'large') => Promise; +} diff --git a/frontend/src/services/System/SystemNativeClient/index.ts b/frontend/src/services/System/SystemNativeClient/index.ts index c426442a..9f554c3a 100644 --- a/frontend/src/services/System/SystemNativeClient/index.ts +++ b/frontend/src/services/System/SystemNativeClient/index.ts @@ -28,7 +28,7 @@ class SystemNativeClient implements SystemClient { }; public setCookie: (key: string, value: string) => void = (key, value) => { - delete window.NativeRobosats?.cookies[key]; + window.NativeRobosats?.loadCookie({ key, value }); void window.NativeRobosats?.postMessage({ category: 'system', type: 'setCookie', diff --git a/frontend/src/services/api/ApiNativeClient/index.ts b/frontend/src/services/api/ApiNativeClient/index.ts index 21620f42..93f8ba39 100644 --- a/frontend/src/services/api/ApiNativeClient/index.ts +++ b/frontend/src/services/api/ApiNativeClient/index.ts @@ -1,8 +1,12 @@ import { type ApiClient, type Auth } from '..'; import { systemClient } from '../../System'; +import ApiWebClient from '../ApiWebClient'; class ApiNativeClient implements ApiClient { - private assetsCache: Record = {}; + public useProxy = true; + + private webClient: ApiClient = new ApiWebClient(); + private readonly assetsPromises = new Map>(); private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { @@ -51,6 +55,7 @@ class ApiNativeClient implements ApiClient { public delete: (baseUrl: string, path: string, auth?: Auth) => Promise = async (baseUrl, path, auth) => { + if (!this.proxy) this.webClient.delete(baseUrl, path, auth); return await window.NativeRobosats?.postMessage({ category: 'http', type: 'delete', @@ -66,6 +71,7 @@ class ApiNativeClient implements ApiClient { body: object, auth?: Auth, ) => Promise = async (baseUrl, path, body, auth) => { + if (!this.proxy) this.webClient.post(baseUrl, path, body, auth); return await window.NativeRobosats?.postMessage({ category: 'http', type: 'post', @@ -81,6 +87,7 @@ class ApiNativeClient implements ApiClient { path, auth, ) => { + if (!this.proxy) this.webClient.get(baseUrl, path, auth); return await window.NativeRobosats?.postMessage({ category: 'http', type: 'get', @@ -89,41 +96,6 @@ class ApiNativeClient implements ApiClient { headers: this.getHeaders(auth), }).then(this.parseResponse); }; - - public fileImageUrl: (baseUrl: string, path: string) => Promise = async ( - baseUrl, - path, - ) => { - if (path === '') { - return await Promise.resolve(''); - } - - if (this.assetsCache[path] != null) { - return await Promise.resolve(this.assetsCache[path]); - } else if (this.assetsPromises.has(path)) { - return await this.assetsPromises.get(path); - } - - this.assetsPromises.set( - path, - new Promise((resolve, reject) => { - window.NativeRobosats?.postMessage({ - category: 'http', - type: 'xhr', - baseUrl, - path, - }) - .then((fileB64: { b64Data: string }) => { - this.assetsCache[path] = `data:image/png;base64,${fileB64.b64Data}`; - this.assetsPromises.delete(path); - resolve(this.assetsCache[path]); - }) - .catch(reject); - }), - ); - - return await this.assetsPromises.get(path); - }; } export default ApiNativeClient; diff --git a/frontend/src/services/api/ApiWebClient/index.ts b/frontend/src/services/api/ApiWebClient/index.ts index 02fe4054..b28edf27 100644 --- a/frontend/src/services/api/ApiWebClient/index.ts +++ b/frontend/src/services/api/ApiWebClient/index.ts @@ -1,6 +1,8 @@ import { type ApiClient, type Auth } from '..'; class ApiWebClient implements ApiClient { + public useProxy = false; + private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { let headers = { 'Content-Type': 'application/json', diff --git a/frontend/src/services/api/index.ts b/frontend/src/services/api/index.ts index b9ac94d8..21b8effd 100644 --- a/frontend/src/services/api/index.ts +++ b/frontend/src/services/api/index.ts @@ -7,11 +7,11 @@ export interface Auth { } export interface ApiClient { + useProxy: boolean; post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise; put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise; get: (baseUrl: string, path: string, auth?: Auth) => Promise; delete: (baseUrl: string, path: string, auth?: Auth) => Promise; - fileImageUrl?: (baseUrl: string, path: string) => Promise; } export const apiClient: ApiClient = diff --git a/frontend/src/utils/federationLottery.ts b/frontend/src/utils/federationLottery.ts index 4768e256..da49be3b 100644 --- a/frontend/src/utils/federationLottery.ts +++ b/frontend/src/utils/federationLottery.ts @@ -45,7 +45,6 @@ export default function federationLottery(federation: Federation): string[] { // federation[shortAlias] = { badges:{ donatesToDevFund }}; // } -// console.log(federation) // return federation; // } @@ -58,5 +57,4 @@ export default function federationLottery(federation: Federation): string[] { // results.push(rankedCoordinators); // } -// console.log(results) // } diff --git a/frontend/static/federation.json b/frontend/static/federation.json index 39dfa51c..4fe2e3fb 100644 --- a/frontend/static/federation.json +++ b/frontend/static/federation.json @@ -25,9 +25,9 @@ "hasLargeLimits": true }, "policies": { - "Policy Name 1": "Experimental coordinator used for development. Use at your own risk.", - "Privacy Policy": "...", - "Data Policy": "..." + "Experimental": "Experimental coordinator used for development. Use at your own risk.", + "Dispute Policy": "Evidence in Disputes: In the event of a dispute, users will be asked to provide transaction-related evidence. This could include transaction IDs, screenshots of payment confirmations, or other pertinent transaction records. Personal information or unrelated transaction details should be redacted to maintain privacy.", + "Non eligible countries": "USA citizens and residents are not allowed to use the Experimental coordinator. F2F transactions are explicitly blocked at creation time for US locations. If a US citizen or resident violates this rule and is found out to be using the Experimental coordinator during a dispute process, they will be denied further service and the dispute mediation will be terminated." }, "mainnet": { "onion": "http://robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion", diff --git a/frontend/static/locales/ca.json b/frontend/static/locales/ca.json index 9846dc02..640e4e82 100644 --- a/frontend/static/locales/ca.json +++ b/frontend/static/locales/ca.json @@ -464,8 +464,8 @@ "The order has expired": "L'ordre ha expirat", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Encara no pots prendre cap ordre! Espera {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "Reps via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "Tu reps via Lightning {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "Tu envies via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "Envies via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "La teva última ordre #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Fosc", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Clar", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/cs.json b/frontend/static/locales/cs.json index 19a3fac0..8e12cb8f 100644 --- a/frontend/static/locales/cs.json +++ b/frontend/static/locales/cs.json @@ -464,8 +464,8 @@ "The order has expired": "Nabídka vypršela", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nabídku nemůžeš zatím příjmout! Počkej {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Přirážka: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Tvá poslední nabídka #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/de.json b/frontend/static/locales/de.json index a9b65825..81d79fbe 100644 --- a/frontend/static/locales/de.json +++ b/frontend/static/locales/de.json @@ -464,8 +464,8 @@ "The order has expired": "Die Order ist abgelaufen", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kannst noch keine Order annehmen! Warte {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Aufschlag: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Deine letzte Order #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/en.json b/frontend/static/locales/en.json index 18cb5a00..a22e5f96 100644 --- a/frontend/static/locales/en.json +++ b/frontend/static/locales/en.json @@ -464,8 +464,8 @@ "The order has expired": "The order has expired", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Your last order #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/es.json b/frontend/static/locales/es.json index c6121544..0faffd60 100644 --- a/frontend/static/locales/es.json +++ b/frontend/static/locales/es.json @@ -464,8 +464,8 @@ "The order has expired": "La orden ha expirado", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "¡No puedes tomar una orden aún! Espera {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Tu última orden #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Oscuro", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Claro", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/eu.json b/frontend/static/locales/eu.json index efe683ab..7f3340a0 100644 --- a/frontend/static/locales/eu.json +++ b/frontend/static/locales/eu.json @@ -464,8 +464,8 @@ "The order has expired": "Eskaera iraungi da", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Oraindik ezin duzu eskaerarik hartu! Itxaron{{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prima: %{{premium}}", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Zure azken eskaera #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/fr.json b/frontend/static/locales/fr.json index a58ee500..746c8e0b 100644 --- a/frontend/static/locales/fr.json +++ b/frontend/static/locales/fr.json @@ -464,8 +464,8 @@ "The order has expired": "L'ordre a expiré", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Vous ne pouvez pas encore prendre un ordre! Attendez {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)", "You receive via {{method}} {{amount}}": "Vous recevez via {{méthode}} {{montant}}", + "You receive {{amount}} Sats (Approx)": "Vous recevez via Lightning {{amount}} Sats (environ)", "You send via Lightning {{amount}} Sats (Approx)": "Vous envoyez via Lightning {{amount}} Sats (environ)", "You send via {{method}} {{amount}}": "Vous envoyez via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prime: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Votre dernière commande #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Sombre", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/it.json b/frontend/static/locales/it.json index bb69dccc..9ec8bfdb 100644 --- a/frontend/static/locales/it.json +++ b/frontend/static/locales/it.json @@ -464,8 +464,8 @@ "The order has expired": "L'ordine è scaduto", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "La posizione appuntata è approssimativa. La posizione esatta del luogo dell'incontro deve essere indicata nella chat crittografata.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Non puoi ancora accettare un ordine! Aspetta {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)", "You receive via {{method}} {{amount}}": "Ricevi {{amount}} via {{method}}", + "You receive {{amount}} Sats (Approx)": "Ricevi {{amount}} Sats via Lightning (approssimativo)", "You send via Lightning {{amount}} Sats (Approx)": "Invii {{amount}} Sats via Lightning (approssimativo)", "You send via {{method}} {{amount}}": "Invii {{amount}} via {{method}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premio: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Il tuo ultimo ordine #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Scuro", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Chiaro", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/ja.json b/frontend/static/locales/ja.json index ce499ed1..c2ecdaae 100644 --- a/frontend/static/locales/ja.json +++ b/frontend/static/locales/ja.json @@ -464,8 +464,8 @@ "The order has expired": "注文は期限切れになりました", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "まだ注文を受け取ることはできません!{{timeMin}}分{{timeSec}}秒待ってください", - "You receive via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を受け取ります", "You receive via {{method}} {{amount}}": "{{method}}で{{amount}}を受け取ります", + "You receive {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を受け取ります", "You send via Lightning {{amount}} Sats (Approx)": "ライトニングで{{amount}} Sats(約)を送信します", "You send via {{method}} {{amount}}": "{{method}}で{{amount}}を送信します", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - プレミアム: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "前回のオーダー #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "ダーク", + "Disabled": "Disabled", "Fiat": "フィアット", "Light": "ライト", "Mainnet": "メインネット", diff --git a/frontend/static/locales/pl.json b/frontend/static/locales/pl.json index 831f6d82..1b2c8cea 100644 --- a/frontend/static/locales/pl.json +++ b/frontend/static/locales/pl.json @@ -464,8 +464,8 @@ "The order has expired": "Zamówienie wygasło", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Nie możesz jeszcze przyjąć zamówienia! Czekać {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premia: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Your last order #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/pt.json b/frontend/static/locales/pt.json index a01345ce..8252db48 100644 --- a/frontend/static/locales/pt.json +++ b/frontend/static/locales/pt.json @@ -464,8 +464,8 @@ "The order has expired": "A ordem expirou", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Você ainda não pode fazer um pedido! Espere {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Prêmio: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Sua última ordem #{{orderID}}", "finished order": "ordem finalizada", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/ru.json b/frontend/static/locales/ru.json index eae1d3dd..335d3892 100644 --- a/frontend/static/locales/ru.json +++ b/frontend/static/locales/ru.json @@ -464,8 +464,8 @@ "The order has expired": "Срок действия ордера истёк", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "Закрепленное местоположение является приблизительным. Точное местоположение места встречи необходимо сообщить в зашифрованном чате.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Вы ещё не можете взять ордер! Подождите {{timeMin}}м {{timeSec}}с", - "You receive via Lightning {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)", "You receive via {{method}} {{amount}}": "Вы получаете через {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "Вы получаете через Lightning {{amount}} Сатоши (приблизительно)", "You send via Lightning {{amount}} Sats (Approx)": "Вы отправляете через Lightning {{amount}} Сатоши (приблизительно)", "You send via {{method}} {{amount}}": "Вы отправляете через {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Наценка: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Ваш последний ордер #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Темный", + "Disabled": "Disabled", "Fiat": "Фиат", "Light": "Светлый", "Mainnet": "Основная сеть", diff --git a/frontend/static/locales/sv.json b/frontend/static/locales/sv.json index 53a8a1a5..328576cf 100644 --- a/frontend/static/locales/sv.json +++ b/frontend/static/locales/sv.json @@ -464,8 +464,8 @@ "The order has expired": "Ordern har förfallit", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Du kan inte ta en order ännu! Vänta {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Din senaste order #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/sw.json b/frontend/static/locales/sw.json index b88b50cf..b07a497c 100644 --- a/frontend/static/locales/sw.json +++ b/frontend/static/locales/sw.json @@ -464,8 +464,8 @@ "The order has expired": "Agizo limekwisha muda", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "Hauwezi kuchukua agizo bado! Subiri {{timeMin}}m {{timeSec}}s", - "You receive via Lightning {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)", "You receive via {{method}} {{amount}}": "Utapokea kupitia {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "Utapokea kupitia Lightning {{amount}} Sats (Takriban)", "You send via Lightning {{amount}} Sats (Approx)": "Utatuma kupitia Lightning {{amount}} Sats (Takriban)", "You send via {{method}} {{amount}}": "Utatuma kupitia {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "Amri yako ya mwisho #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Giza", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Nuru", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/th.json b/frontend/static/locales/th.json index 159a7871..c96bc579 100644 --- a/frontend/static/locales/th.json +++ b/frontend/static/locales/th.json @@ -464,8 +464,8 @@ "The order has expired": "รายการหมดอายุแล้ว", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "คุณยังไม่สามารถดำเนินรายการได้! รออีก {{timeMin}} นาที {{timeSec}} วินาที", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "You receive via {{method}} {{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "You send via {{method}} {{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - ค่าพรีเมี่ยม: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "รายการล่าสุดของคุณ #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "Dark", + "Disabled": "Disabled", "Fiat": "Fiat", "Light": "Light", "Mainnet": "Mainnet", diff --git a/frontend/static/locales/zh-SI.json b/frontend/static/locales/zh-SI.json index 1c4644eb..671f4b67 100644 --- a/frontend/static/locales/zh-SI.json +++ b/frontend/static/locales/zh-SI.json @@ -464,8 +464,8 @@ "The order has expired": "订单已到期", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暂时还不能吃单!请等{{timeMin}}分 {{timeSec}}秒", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "你通过{{method}}接收{{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "你通过{{method}}发送{{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢价: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "你的上一笔交易 #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "深色", + "Disabled": "Disabled", "Fiat": "法币", "Light": "浅色", "Mainnet": "主网", diff --git a/frontend/static/locales/zh-TR.json b/frontend/static/locales/zh-TR.json index d5abf5ce..8b2c666e 100644 --- a/frontend/static/locales/zh-TR.json +++ b/frontend/static/locales/zh-TR.json @@ -464,8 +464,8 @@ "The order has expired": "訂單已到期", "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.": "The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.", "You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s": "你暫時還不能吃單!請等{{timeMin}}分 {{timeSec}}秒", - "You receive via Lightning {{amount}} Sats (Approx)": "You receive via Lightning {{amount}} Sats (Approx)", "You receive via {{method}} {{amount}}": "你通過{{method}}接收{{amount}}", + "You receive {{amount}} Sats (Approx)": "You receive {{amount}} Sats (Approx)", "You send via Lightning {{amount}} Sats (Approx)": "You send via Lightning {{amount}} Sats (Approx)", "You send via {{method}} {{amount}}": "你通過{{method}}發送{{amount}}", "{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%": "{{price}} {{currencyCode}}/BTC - 溢價: {{premium}}%", @@ -489,7 +489,9 @@ "Your last order #{{orderID}}": "你的上一筆交易 #{{orderID}}", "finished order": "finished order", "#43": "Phrases in components/SettingsForm/index.tsx", + "Build-in": "Build-in", "Dark": "深色", + "Disabled": "Disabled", "Fiat": "法幣", "Light": "淺色", "Mainnet": "主網", diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index d3625542..442ae97c 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -56,6 +56,15 @@ const configMobile: Configuration = { async: true, }, }, + { + test: path.resolve(__dirname, 'src/services/Roboidentities/Web.ts'), + loader: 'file-replace-loader', + options: { + condition: 'if-replacement-exists', + replacement: path.resolve(__dirname, 'src/services/Roboidentities/Native.ts'), + async: true, + }, + }, { test: path.resolve(__dirname, 'src/components/RobotAvatar/placeholder.json'), loader: 'file-replace-loader', @@ -81,6 +90,10 @@ const configMobile: Configuration = { from: path.resolve(__dirname, 'static/assets/sounds'), to: path.resolve(__dirname, '../mobile/html/Web.bundle/assets/sounds'), }, + { + from: path.resolve(__dirname, 'static/federation'), + to: path.resolve(__dirname, '../mobile/html/Web.bundle/assets/federation'), + }, ], }), ], diff --git a/mobile/App.tsx b/mobile/App.tsx index 66a2babe..e2977573 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -1,23 +1,45 @@ -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { WebView, WebViewMessageEvent } from 'react-native-webview'; -import { SafeAreaView, Text, Platform, Appearance } from 'react-native'; +import { SafeAreaView, Text, Platform, Appearance, DeviceEventEmitter } from 'react-native'; import TorClient from './services/Tor'; import Clipboard from '@react-native-clipboard/clipboard'; -import NetInfo from '@react-native-community/netinfo'; import EncryptedStorage from 'react-native-encrypted-storage'; import { name as app_name, version as app_version } from './package.json'; +import TorModule from './native/TorModule'; +import RoboIdentitiesModule from './native/RoboIdentitiesModule'; const backgroundColors = { light: 'white', dark: 'black', }; +export type TorStatus = 'ON' | 'STARTING' | 'STOPPING' | 'OFF'; + const App = () => { const colorScheme = Appearance.getColorScheme() ?? 'light'; const torClient = new TorClient(); const webViewRef = useRef(); const uri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/index.html'; + useEffect(() => { + TorModule.start(); + DeviceEventEmitter.addListener('TorStatus', (payload) => { + if (payload.torStatus === 'OFF') TorModule.restart(); + injectMessage({ + category: 'system', + type: 'torStatus', + detail: payload.torStatus, + }); + }); + }, []); + + useEffect(() => { + const interval = setInterval(() => { + TorModule.getTorStatus(); + }, 2000); + return () => clearInterval(interval); + }, []); + const injectMessageResolve = (id: string, data?: object) => { const json = JSON.stringify(data || {}); webViewRef.current?.injectJavaScript( @@ -49,6 +71,7 @@ const App = () => { loadCookie('settings_mode'); loadCookie('settings_light_qr'); loadCookie('settings_network'); + loadCookie('settings_use_proxy'); loadCookie('garage_slots').then(() => injectMessageResolve(responseId)); }; @@ -72,7 +95,7 @@ const App = () => { const onMessage = async (event: WebViewMessageEvent) => { const data = JSON.parse(event.nativeEvent.data); if (data.category === 'http') { - sendTorStatus(); + TorModule.getTorStatus(); if (data.type === 'get') { torClient .get(data.baseUrl, data.path, data.headers) @@ -80,7 +103,7 @@ const App = () => { injectMessageResolve(data.id, response); }) .catch((e) => onCatch(data.id, e)) - .finally(sendTorStatus); + .finally(TorModule.getTorStatus); } else if (data.type === 'post') { torClient .post(data.baseUrl, data.path, data.body, data.headers) @@ -88,7 +111,7 @@ const App = () => { injectMessageResolve(data.id, response); }) .catch((e) => onCatch(data.id, e)) - .finally(sendTorStatus); + .finally(TorModule.getTorStatus); } else if (data.type === 'delete') { torClient .delete(data.baseUrl, data.path, data.headers) @@ -96,15 +119,7 @@ const App = () => { injectMessageResolve(data.id, response); }) .catch((e) => onCatch(data.id, e)) - .finally(sendTorStatus); - } else if (data.type === 'xhr') { - torClient - .request(data.baseUrl, data.path) - .then((response: object) => { - injectMessageResolve(data.id, response); - }) - .catch((e) => onCatch(data.id, e)) - .finally(sendTorStatus); + .finally(TorModule.getTorStatus); } } else if (data.category === 'system') { if (data.type === 'init') { @@ -116,6 +131,14 @@ const App = () => { } else if (data.type === 'deleteCookie') { EncryptedStorage.removeItem(data.key); } + } else if (data.category === 'roboidentities') { + if (data.type === 'roboname') { + const roboname = await RoboIdentitiesModule.generateRoboname(data.detail); + injectMessageResolve(data.id, { roboname }); + } else if (data.type === 'robohash') { + const robohash = await RoboIdentitiesModule.generateRobohash(data.detail); + injectMessageResolve(data.id, { robohash }); + } } }; @@ -132,23 +155,6 @@ const App = () => { } catch (error) {} }; - const sendTorStatus = async (event?: any) => { - NetInfo.fetch().then(async (state) => { - let daemonStatus = 'ERROR'; - if (state.isInternetReachable) { - try { - daemonStatus = await torClient.daemon.getDaemonStatus(); - } catch {} - } - - injectMessage({ - category: 'system', - type: 'torStatus', - detail: daemonStatus, - }); - }); - }; - return ( + android:theme="@style/AppTheme" + android:extractNativeLibs="true" + > packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); + packages.add(new RobosatsPackage()); + return packages; } diff --git a/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java b/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java new file mode 100644 index 00000000..c37f8505 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java @@ -0,0 +1,22 @@ +package com.robosats; + +import android.util.Log; + +public class RoboIdentities { + static { + System.loadLibrary("robonames"); + System.loadLibrary("robohash"); + } + + public String generateRoboname(String initial_string) { + return nativeGenerateRoboname(initial_string); + } + + public String generateRobohash(String initial_string) { + return nativeGenerateRobohash(initial_string); + } + + // Native functions implemented in Rust. + private static native String nativeGenerateRoboname(String initial_string); + private static native String nativeGenerateRobohash(String initial_string); +} diff --git a/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java new file mode 100644 index 00000000..2eca0080 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java @@ -0,0 +1,30 @@ +package com.robosats; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; +import com.robosats.modules.RoboIdentitiesModule; +import com.robosats.modules.TorModule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RobosatsPackage implements ReactPackage { + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new TorModule(reactContext)); + modules.add(new RoboIdentitiesModule(reactContext)); + + return modules; + } +} diff --git a/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java b/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java new file mode 100644 index 00000000..c18d131b --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java @@ -0,0 +1,37 @@ +package com.robosats.modules; + +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.robosats.RoboIdentities; + +public class RoboIdentitiesModule extends ReactContextBaseJavaModule { + private ReactApplicationContext context; + + public RoboIdentitiesModule(ReactApplicationContext reactContext) { + context = reactContext; + } + + @Override + public String getName() { + return "RoboIdentitiesModule"; + } + + @ReactMethod + public void generateRoboname(String initial_string, final Promise promise) { + String roboname = new RoboIdentities().generateRoboname(initial_string); + promise.resolve(roboname); + } + + @ReactMethod + public void generateRobohash(String initial_string, final Promise promise) { + String robohash = new RoboIdentities().generateRobohash(initial_string); + promise.resolve(robohash); + } +} diff --git a/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java new file mode 100644 index 00000000..1266b152 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java @@ -0,0 +1,158 @@ +package com.robosats.modules; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.robosats.tor.TorKmpManager; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + + +public class TorModule extends ReactContextBaseJavaModule { + private TorKmpManager torKmpManager; + private ReactApplicationContext context; + public TorModule(ReactApplicationContext reactContext) { + context = reactContext; + } + + @Override + public String getName() { + return "TorModule"; + } + + @ReactMethod + public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException { + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout + .readTimeout(30, TimeUnit.SECONDS) // Set read timeout + .proxy(torKmpManager.getProxy()).build(); + + Request.Builder requestBuilder = new Request.Builder().url(url); + + JSONObject headersObject = new JSONObject(headers); + headersObject.keys().forEachRemaining(key -> { + String value = headersObject.optString(key); + requestBuilder.addHeader(key, value); + }); + + if (Objects.equals(action, "DELETE")) { + requestBuilder.delete(); + } else if (Objects.equals(action, "POST")) { + RequestBody requestBody = RequestBody.create(body, MediaType.get("application/json; charset=utf-8")); + requestBuilder.post(requestBody); + } else { + requestBuilder.get(); + } + + Request request = requestBuilder.build(); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + Log.d("RobosatsError", e.toString()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + String body = response.body() != null ? response.body().string() : "{}"; + JSONObject headersJson = new JSONObject(); + response.headers().names().forEach(name -> { + try { + headersJson.put(name, response.header(name)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + }); + promise.resolve("{\"json\":" + body + ", \"headers\": " + headersJson +"}"); + } + }); + } + + @ReactMethod + public void getTorStatus() { + String torState = torKmpManager.getTorState().getState().name(); + WritableMap payload = Arguments.createMap(); + payload.putString("torStatus", torState); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorStatus", payload); + } + + @ReactMethod + public void isConnected() { + String isConnected = String.valueOf(torKmpManager.isConnected()); + WritableMap payload = Arguments.createMap(); + payload.putString("isConnected", isConnected); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorIsConnected", payload); + } + + @ReactMethod + public void isStarting() { + String isStarting = String.valueOf(torKmpManager.isStarting()); + WritableMap payload = Arguments.createMap(); + payload.putString("isStarting", isStarting); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorIsStarting", payload); + } + + @ReactMethod + public void stop() { + torKmpManager.getTorOperationManager().stopQuietly(); + WritableMap payload = Arguments.createMap(); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorStop", payload); + } + + @ReactMethod + public void start() { + torKmpManager = new TorKmpManager(context.getCurrentActivity().getApplication()); + torKmpManager.getTorOperationManager().startQuietly(); + WritableMap payload = Arguments.createMap(); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorStart", payload); + } + + @ReactMethod + public void restart() { + torKmpManager = new TorKmpManager(context.getCurrentActivity().getApplication()); + torKmpManager.getTorOperationManager().restartQuietly(); + WritableMap payload = Arguments.createMap(); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorRestart", payload); + } + + @ReactMethod + public void newIdentity() { + torKmpManager.newIdentity(context.getCurrentActivity().getApplication()); + WritableMap payload = Arguments.createMap(); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("TorNewIdentity", payload); + } +} diff --git a/mobile/android/app/src/main/java/com/robosats/tor/EnumTorState.kt b/mobile/android/app/src/main/java/com/robosats/tor/EnumTorState.kt new file mode 100644 index 00000000..8ddc7da5 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/tor/EnumTorState.kt @@ -0,0 +1,8 @@ +package com.robosats.tor + +enum class EnumTorState { + STARTING, + ON, + STOPPING, + OFF +} diff --git a/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt b/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt new file mode 100644 index 00000000..bed7fbc5 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt @@ -0,0 +1,389 @@ +package com.robosats.tor + +import android.app.Application +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.matthewnelson.kmp.tor.KmpTorLoaderAndroid +import io.matthewnelson.kmp.tor.TorConfigProviderAndroid +import io.matthewnelson.kmp.tor.common.address.* +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Option.* +import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Setting.* +import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlInfoGet +import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal +import io.matthewnelson.kmp.tor.controller.common.events.TorEvent +import io.matthewnelson.kmp.tor.manager.TorManager +import io.matthewnelson.kmp.tor.manager.TorServiceConfig +import io.matthewnelson.kmp.tor.manager.common.TorControlManager +import io.matthewnelson.kmp.tor.manager.common.TorOperationManager +import io.matthewnelson.kmp.tor.manager.common.event.TorManagerEvent +import io.matthewnelson.kmp.tor.manager.common.state.isOff +import io.matthewnelson.kmp.tor.manager.common.state.isOn +import io.matthewnelson.kmp.tor.manager.common.state.isStarting +import io.matthewnelson.kmp.tor.manager.common.state.isStopping +import io.matthewnelson.kmp.tor.manager.R +import kotlinx.coroutines.* +import java.net.InetSocketAddress +import java.net.Proxy + +class TorKmpManager(application : Application) { + + private val TAG = "TorListener" + + private val providerAndroid by lazy { + object : TorConfigProviderAndroid(context = application) { + override fun provide(): TorConfig { + return TorConfig.Builder { + // Set multiple ports for all of the things + val dns = Ports.Dns() + put(dns.set(AorDorPort.Value(PortProxy(9252)))) + put(dns.set(AorDorPort.Value(PortProxy(9253)))) + + val socks = Ports.Socks() + put(socks.set(AorDorPort.Value(PortProxy(9254)))) + put(socks.set(AorDorPort.Value(PortProxy(9255)))) + + val http = Ports.HttpTunnel() + put(http.set(AorDorPort.Value(PortProxy(9258)))) + put(http.set(AorDorPort.Value(PortProxy(9259)))) + + val trans = Ports.Trans() + put(trans.set(AorDorPort.Value(PortProxy(9262)))) + put(trans.set(AorDorPort.Value(PortProxy(9263)))) + + // If a port (9263) is already taken (by ^^^^ trans port above) + // this will take its place and "overwrite" the trans port entry + // because port 9263 is taken. + put(socks.set(AorDorPort.Value(PortProxy(9263)))) + + // Set Flags + socks.setFlags(setOf( + Ports.Socks.Flag.OnionTrafficOnly + )).setIsolationFlags(setOf( + Ports.IsolationFlag.IsolateClientAddr, + )).set(AorDorPort.Value(PortProxy(9264))) + put(socks) + + // reset our socks object to defaults + socks.setDefault() + + // Not necessary, as if ControlPort is missing it will be + // automatically added for you; but for demonstration purposes... +// put(Ports.Control().set(AorDorPort.Auto)) + + // Use a UnixSocket instead of TCP for the ControlPort. + // + // A unix domain socket will always be preferred on Android + // if neither Ports.Control or UnixSockets.Control are provided. + put(UnixSockets.Control().set(FileSystemFile( + workDir.builder { + + // Put the file in the "data" directory + // so that we avoid any directory permission + // issues. + // + // Note that DataDirectory is automatically added + // for you if it is not present in your provided + // config. If you set a custom Path for it, you + // should use it here. + addSegment(DataDirectory.DEFAULT_NAME) + + addSegment(UnixSockets.Control.DEFAULT_NAME) + } + ))) + + // Use a UnixSocket instead of TCP for the SocksPort. + put(UnixSockets.Socks().set(FileSystemFile( + workDir.builder { + + // Put the file in the "data" directory + // so that we avoid any directory permission + // issues. + // + // Note that DataDirectory is automatically added + // for you if it is not present in your provided + // config. If you set a custom Path for it, you + // should use it here. + addSegment(DataDirectory.DEFAULT_NAME) + + addSegment(UnixSockets.Socks.DEFAULT_NAME) + } + ))) + + // For Android, disabling & reducing connection padding is + // advisable to minimize mobile data usage. + put(ConnectionPadding().set(AorTorF.False)) + put(ConnectionPaddingReduced().set(TorF.True)) + + // Tor default is 24h. Reducing to 10 min helps mitigate + // unnecessary mobile data usage. + put(DormantClientTimeout().set(Time.Minutes(10))) + + // Tor defaults this setting to false which would mean if + // Tor goes dormant, the next time it is started it will still + // be in the dormant state and will not bootstrap until being + // set to "active". This ensures that if it is a fresh start, + // dormancy will be cancelled automatically. + put(DormantCanceledByStartup().set(TorF.True)) + + // If planning to use v3 Client Authentication in a persistent + // manner (where private keys are saved to disk via the "Persist" + // flag), this is needed to be set. + put(ClientOnionAuthDir().set(FileSystemDir( + workDir.builder { addSegment(ClientOnionAuthDir.DEFAULT_NAME) } + ))) + + val hsPath = workDir.builder { + addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME) + addSegment("test_service") + } + // Add Hidden services + put(HiddenService() + .setPorts(ports = setOf( + // Use a unix domain socket to communicate via IPC instead of over TCP + HiddenService.UnixSocket(virtualPort = Port(80), targetUnixSocket = hsPath.builder { + addSegment(HiddenService.UnixSocket.DEFAULT_UNIX_SOCKET_NAME) + }), + )) + .setMaxStreams(maxStreams = HiddenService.MaxStreams(value = 2)) + .setMaxStreamsCloseCircuit(value = TorF.True) + .set(FileSystemDir(path = hsPath)) + ) + + put(HiddenService() + .setPorts(ports = setOf( + HiddenService.Ports(virtualPort = Port(80), targetPort = Port(1030)), // http + HiddenService.Ports(virtualPort = Port(443), targetPort = Port(1030)) // https + )) + .set(FileSystemDir(path = + workDir.builder { + addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME) + addSegment("test_service_2") + } + )) + ) + }.build() + } + } + } + + private val loaderAndroid by lazy { + KmpTorLoaderAndroid(provider = providerAndroid) + } + + private val manager: TorManager by lazy { + TorManager.newInstance(application = application, loader = loaderAndroid, requiredEvents = null) + } + + // only expose necessary interfaces + val torOperationManager: TorOperationManager get() = manager + val torControlManager: TorControlManager get() = manager + + private val listener = TorListener() + + val events: LiveData get() = listener.eventLines + + private val appScope by lazy { + CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) + } + + val torStateLiveData: MutableLiveData = MutableLiveData() + get() = field + var torState: TorState = TorState() + get() = field + + var proxy: Proxy? = null + get() = field + + init { + manager.debug(true) + manager.addListener(listener) + listener.addLine(TorServiceConfig.getMetaData(application).toString()) + } + + fun isConnected(): Boolean { + return manager.state.isOn() && manager.state.bootstrap >= 100 + } + + fun isStarting(): Boolean { + return manager.state.isStarting() || + (manager.state.isOn() && manager.state.bootstrap < 100); + } + + + fun newIdentity(appContext: Application) { + appScope.launch { + val result = manager.signal(TorControlSignal.Signal.NewNym) + result.onSuccess { + if (it !is String) { + listener.addLine(TorControlSignal.NEW_NYM_SUCCESS) + Toast.makeText(appContext, TorControlSignal.NEW_NYM_SUCCESS, Toast.LENGTH_SHORT).show() + return@onSuccess + } + + val post: String? = when { + it.startsWith(TorControlSignal.NEW_NYM_RATE_LIMITED) -> { + // Rate limiting NEWNYM request: delaying by 8 second(s) + val seconds: Int? = it.drop(TorControlSignal.NEW_NYM_RATE_LIMITED.length) + .substringBefore(' ') + .toIntOrNull() + + if (seconds == null) { + it + } else { + appContext.getString( + R.string.kmp_tor_newnym_rate_limited, + seconds + ) + } + } + it == TorControlSignal.NEW_NYM_SUCCESS -> { + appContext.getString(R.string.kmp_tor_newnym_success) + } + else -> { + null + } + } + + if (post != null) { + listener.addLine(post) + Toast.makeText(appContext, post, Toast.LENGTH_SHORT).show() + } + } + result.onFailure { + val msg = "Tor identity change failed" + listener.addLine(msg) + Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show() + } + } + } + + + private inner class TorListener: TorManagerEvent.Listener() { + private val _eventLines: MutableLiveData = MutableLiveData("") + val eventLines: LiveData = _eventLines + private val events: MutableList = ArrayList(50) + fun addLine(line: String) { + synchronized(this) { + if (events.size > 49) { + events.removeAt(0) + } + events.add(line) + //Log.i(TAG, line) + //_eventLines.value = events.joinToString("\n") + _eventLines.postValue(events.joinToString("\n")) + } + } + + override fun onEvent(event: TorManagerEvent) { + + if (event is TorManagerEvent.State) { + val stateEvent: TorManagerEvent.State = event + val state = stateEvent.torState + torState.progressIndicator = state.bootstrap + val liveTorState = TorState() + liveTorState.progressIndicator = state.bootstrap + + if (state.isOn()) { + if (state.bootstrap >= 100) { + torState.state = EnumTorState.ON + liveTorState.state = EnumTorState.ON + } else { + torState.state = EnumTorState.STARTING + liveTorState.state = EnumTorState.STARTING + } + } else if (state.isStarting()) { + torState.state = EnumTorState.STARTING + liveTorState.state = EnumTorState.STARTING + } else if (state.isOff()) { + torState.state = EnumTorState.OFF + liveTorState.state = EnumTorState.OFF + } else if (state.isStopping()) { + torState.state = EnumTorState.STOPPING + liveTorState.state = EnumTorState.STOPPING + } + torStateLiveData.postValue(liveTorState) + } + addLine(event.toString()) + super.onEvent(event) + } + + override fun onEvent(event: TorEvent.Type.SingleLineEvent, output: String) { + addLine("$event - $output") + + super.onEvent(event, output) + } + + override fun onEvent(event: TorEvent.Type.MultiLineEvent, output: List) { + addLine("multi-line event: $event. See Logs.") + + // these events are many many many lines and should be moved + // off the main thread if ever needed to be dealt with. + val enabled = false + if (enabled) { + appScope.launch(Dispatchers.IO) { + Log.d(TAG, "-------------- multi-line event START: $event --------------") + for (line in output) { + Log.d(TAG, line) + } + Log.d(TAG, "--------------- multi-line event END: $event ---------------") + } + } + + super.onEvent(event, output) + } + + override fun managerEventError(t: Throwable) { + t.printStackTrace() + } + + override fun managerEventAddressInfo(info: TorManagerEvent.AddressInfo) { + if (info.isNull) { + // Tear down HttpClient + } else { + info.socksInfoToProxyAddressOrNull()?.firstOrNull()?.let { proxyAddress -> + @Suppress("UNUSED_VARIABLE") + val socket = InetSocketAddress(proxyAddress.address.value, proxyAddress.port.value) + proxy = Proxy(Proxy.Type.SOCKS, socket) + } + } + } + + override fun managerEventStartUpCompleteForTorInstance() { + // Do one-time things after we're bootstrapped + + appScope.launch { + torControlManager.onionAddNew( + type = OnionAddress.PrivateKey.Type.ED25519_V3, + hsPorts = setOf(HiddenService.Ports(virtualPort = Port(443))), + flags = null, + maxStreams = null, + ).onSuccess { hsEntry -> + addLine( + "New HiddenService: " + + "\n - Address: https://${hsEntry.address.canonicalHostname()}" + + "\n - PrivateKey: ${hsEntry.privateKey}" + ) + + torControlManager.onionDel(hsEntry.address).onSuccess { + addLine("Aaaaaaaaand it's gone...") + }.onFailure { t -> + t.printStackTrace() + } + }.onFailure { t -> + t.printStackTrace() + } + + delay(20_000L) + + torControlManager.infoGet(TorControlInfoGet.KeyWord.Uptime()).onSuccess { uptime -> + addLine("Uptime - $uptime") + }.onFailure { t -> + t.printStackTrace() + } + } + } + } +} diff --git a/mobile/android/app/src/main/java/com/robosats/tor/TorState.kt b/mobile/android/app/src/main/java/com/robosats/tor/TorState.kt new file mode 100644 index 00000000..51052571 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/tor/TorState.kt @@ -0,0 +1,14 @@ +package com.robosats.tor + +class TorState { + var state : EnumTorState = EnumTorState.OFF + get() = field + set(value) { + field = value + } + var progressIndicator : Int = 0 + get() = field + set(value) { + field = value + } +} diff --git a/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so new file mode 100755 index 00000000..8bc01999 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so differ diff --git a/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so new file mode 100755 index 00000000..3e13c6d8 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so differ diff --git a/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so new file mode 100755 index 00000000..3775d2f0 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so differ diff --git a/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so new file mode 100755 index 00000000..2cf9568e Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so differ diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index 322d3562..19e52517 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -9,7 +9,6 @@ buildscript { compileSdkVersion = 33 targetSdkVersion = 33 kotlin_version = "1.8.21" - kotlinVersion = "1.8.21" //for react-native-tor if (System.properties['os.arch'] == "aarch64") { // For M1 Users we need to use the NDK 24 which added support for aarch64 diff --git a/mobile/fastlane/metadata/en-US/full_description.txt b/mobile/fastlane/metadata/en-US/full_description.txt new file mode 100644 index 00000000..8b31ea67 --- /dev/null +++ b/mobile/fastlane/metadata/en-US/full_description.txt @@ -0,0 +1,12 @@ +

RoboSats is a simple and private app to exchange bitcoin for national currencies. Robosats simplifies the P2P user experience and uses lightning hold invoices to minimize custody and trust requirements. The deterministically generated robot avatars help users stick to best privacy practices.

+


Features:

    +
  • Privacy focused: your robot avatar is deterministically generated, no need for registration.
  • +
  • More than 10 languages available and over 60 fiat currencies
  • +
  • Safe: simply lock a lightning hodl invoice and show you are real and committed.
  • +
  • No data collection. Your communication with your peer is PGP encrypted, only you can read it.
  • +
  • Lightning fast: the average sovereign trade finishes in ~ 8 minutes. Faster than a single block confirmation!
  • +
  • Fully collateralized escrow: your peer is always committed and cannot run away with the funds.
  • +
  • Strong incentives system: attempts of cheating are penalized with the slashing of the Sats in the fidelity bond.
  • +
  • Guides and video tutorials available at https://learn.robosats.com/watch/en
  • +
+

You can join other cool Robots and get community support at our Telegram group.

\ No newline at end of file diff --git a/mobile/fastlane/metadata/en-US/images/icon.png b/mobile/fastlane/metadata/en-US/images/icon.png new file mode 100644 index 00000000..1acd5bd2 Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/icon.png differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg new file mode 100644 index 00000000..0e8c2168 Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/01.jpg differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg new file mode 100644 index 00000000..fe0fa90f Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/02.jpg differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg new file mode 100644 index 00000000..b900c054 Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/03.jpg differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg new file mode 100644 index 00000000..1fa2a63b Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/04.jpg differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg new file mode 100644 index 00000000..ebe70da6 Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/05.jpg differ diff --git a/mobile/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg new file mode 100644 index 00000000..b44133af Binary files /dev/null and b/mobile/fastlane/metadata/en-US/images/phoneScreenshots/06.jpg differ diff --git a/mobile/fastlane/metadata/en-US/short_description.txt b/mobile/fastlane/metadata/en-US/short_description.txt new file mode 100644 index 00000000..a5607740 --- /dev/null +++ b/mobile/fastlane/metadata/en-US/short_description.txt @@ -0,0 +1 @@ +Simple and private app to exchange bitcoin for national currencies. \ No newline at end of file diff --git a/mobile/native/RoboIdentitiesModule.ts b/mobile/native/RoboIdentitiesModule.ts new file mode 100644 index 00000000..a5c3c83e --- /dev/null +++ b/mobile/native/RoboIdentitiesModule.ts @@ -0,0 +1,9 @@ +import { NativeModules } from 'react-native'; +const { RoboIdentitiesModule } = NativeModules; + +interface RoboIdentitiesModuleInterface { + generateRoboname: (initialString: String) => Promise; + generateRobohash: (initialString: String) => Promise; +} + +export default RoboIdentitiesModule as RoboIdentitiesModuleInterface; diff --git a/mobile/native/TorModule.ts b/mobile/native/TorModule.ts new file mode 100644 index 00000000..d071aeee --- /dev/null +++ b/mobile/native/TorModule.ts @@ -0,0 +1,11 @@ +import { NativeModules } from 'react-native'; +const { TorModule } = NativeModules; + +interface TorModuleInterface { + start: () => void; + restart: () => void; + getTorStatus: () => void; + sendRequest: (action: string, url: string, headers: string, body: string) => Promise; +} + +export default TorModule as TorModuleInterface; diff --git a/mobile/package-lock.json b/mobile/package-lock.json index a98f3056..c31881b6 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -1,19 +1,18 @@ { "name": "robosats", - "version": "0.6.0", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "robosats", - "version": "0.6.0", + "version": "0.6.2", "dependencies": { "@react-native-clipboard/clipboard": "^1.13.2", "@react-native-community/netinfo": "^11.3.0", "react": "18.2.0", "react-native": "^0.71.8", "react-native-encrypted-storage": "^4.0.3", - "react-native-tor": "^0.1.8", "react-native-webview": "^13.3.0" }, "devDependencies": { @@ -35,10 +34,10 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^4.6.2", "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.75.1", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "react-test-renderer": "18.2.0", "typescript": "^5.4.5" } @@ -4330,10 +4329,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@types/async": { - "version": "3.2.20", - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", @@ -7056,9 +7051,10 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -12718,9 +12714,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -12971,18 +12967,6 @@ "version": "0.71.18", "license": "MIT" }, - "node_modules/react-native-tor": { - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@types/async": "^3.2.6", - "async": "^3.2.0" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-webview": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.3.0.tgz", diff --git a/mobile/package.json b/mobile/package.json index c213d06d..e45c135c 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -1,6 +1,6 @@ { "name": "robosats", - "version": "0.6.0", + "version": "0.6.2", "private": true, "scripts": { "android": "react-native run-android", @@ -17,7 +17,6 @@ "react": "18.2.0", "react-native": "^0.71.8", "react-native-encrypted-storage": "^4.0.3", - "react-native-tor": "^0.1.8", "react-native-webview": "^13.3.0" }, "devDependencies": { @@ -39,10 +38,10 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^4.6.2", "jest": "^29.7.0", "metro-react-native-babel-preset": "^0.75.1", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "react-test-renderer": "18.2.0", "typescript": "^5.4.5" }, diff --git a/mobile/services/Tor/index.ts b/mobile/services/Tor/index.ts index 35b4f2c4..15e22d0a 100644 --- a/mobile/services/Tor/index.ts +++ b/mobile/services/Tor/index.ts @@ -1,29 +1,6 @@ -import Tor from 'react-native-tor'; +import TorModule from '../../native/TorModule'; class TorClient { - daemon: ReturnType; - - constructor() { - this.daemon = Tor({ - stopDaemonOnBackground: false, - numberConcurrentRequests: 0, - }); - } - - private readonly connectDaemon: () => void = async () => { - try { - this.daemon.startIfNotStarted(); - } catch { - console.log('TOR already started'); - } - }; - - public reset: () => void = async () => { - console.log('Reset TOR'); - await this.daemon.stopIfRunning(); - await this.daemon.startIfNotStarted(); - }; - public get: (baseUrl: string, path: string, headers: object) => Promise = async ( baseUrl, path, @@ -31,9 +8,13 @@ class TorClient { ) => { return await new Promise(async (resolve, reject) => { try { - const response = await this.daemon.get(`${baseUrl}${path}`, headers); - - resolve(response); + const response = await TorModule.sendRequest( + 'GET', + `${baseUrl}${path}`, + JSON.stringify(headers), + '{}', + ); + resolve(JSON.parse(response)); } catch (error) { reject(error); } @@ -47,28 +28,13 @@ class TorClient { ) => { return await new Promise(async (resolve, reject) => { try { - const response = await this.daemon.delete(`${baseUrl}${path}`, '', headers); - - resolve(response); - } catch (error) { - reject(error); - } - }); - }; - - public request: (baseUrl: string, path: string) => Promise = async ( - baseUrl: string, - path, - ) => { - return await new Promise(async (resolve, reject) => { - try { - const response = await this.daemon - .request(`${baseUrl}${path}`, 'GET', '', {}, true) - .then((resp) => { - resolve(resp); - }); - - resolve(response); + const response = await TorModule.sendRequest( + 'DELETE', + `${baseUrl}${path}`, + JSON.stringify(headers), + '{}', + ); + resolve(JSON.parse(response)); } catch (error) { reject(error); } @@ -80,9 +46,13 @@ class TorClient { return await new Promise(async (resolve, reject) => { try { const json = JSON.stringify(body); - const response = await this.daemon.post(`${baseUrl}${path}`, json, headers); - - resolve(response); + const response = await TorModule.sendRequest( + 'POST', + `${baseUrl}${path}`, + JSON.stringify(headers), + json, + ); + resolve(JSON.parse(response)); } catch (error) { reject(error); } diff --git a/release_notes.md b/release_notes.md index fe2f9c91..2dfa8571 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,41 +1,38 @@ -RoboSats v0.6.0 is now out! :rocket: +RoboSats v0.6.2 is now out! :rocket: # Changes -## New Features -### Decentralization -RoboSats v0.6.0 introduces a major upgrade, introducing the RoboSats Federation, a decentralized system of independent coordinators to host orders, enhancing the platform's robustness and user experience. This version is a significant step towards decentralization, allowing users to interact with any coordinator seamlessly. - -It's crucial to choose trustworthy coordinators due to the potential risks of malicious activity. The federated client is available for testing at specific URLs, with a stable release planned. Key features include multiple coordinators competing for users, decentralized instances for increased robustness, and a focus on coordinators profiles and trust. - -Learn more in https://learn.robosats.com/robosats/update/pre-release-robosats-decentralized/ - -### New avatar generator -Your Robot identity is now generated in your client app, when previously, the robot identity was created by the coordinator. This allows now for super-fast Robot avatar and nickname generation that works even if your connection to the coordinators is down. The new robot avatars, are in fact, more diverse and better looking, however, the same token will now yield a different avatar when compared to v0.5.4 (but the robot identity remains the same, also keeping the same nickname). - -## Bug Fixes and Performance Improvements -The whole app architecture is new. There might be new bugs, solved bugs, worse performance and better performance: who knows!! :D - -## Special thanks -Special thanks to @KoalaSat who has driven some of the largest development pushes needed to get The Federation Layer fully working. +## What's new +In v0.6.2 we only fix a regression introduced in v0.6.2 that makes the depth chart crash. # Android -The Android app is currently not supported on this early phase of the Federated app. +**[Click to download universal RoboSats APK for Android](https://github.com/RoboSats/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk)** +Smaller bundles for each CPU architecture available in the attachments. +### Verify the app using GPG: + +1. [Download the ascii armored signature](https://github.com/Reckless-Satoshi/robosats/releases/download/v0.6.2-alpha/robosats-v0.6.2.alpha-universal.apk.asc) + +2. Run this command on a directory that contains the apk file and and the ascii armored signature. +`gpg --verify robosats-v0.6.2.alpha-universal.apk.asc` + +3. Verify the signer is actually Reckless-Satoshi (fingerprints match): [B4AB5F19113D4125DDF217739C4585B561315571](https://keys.openpgp.org/vks/v1/by-fingerprint/B4AB5F19113D4125DDF217739C4585B561315571) + +Alternatively you can also verify with the release with the SHA256 checksum. # Docker Images -[Coordinator Backend Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.0-alpha) +[Coordinator Backend Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats/tags?page=1&name=v0.6.2-alpha) ```bash -docker pull recksato/robosats:v0.6.0-alpha +docker pull recksato/robosats:v0.6.2-alpha ``` -[Client App Image v0.6.0-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.0-alpha) +[Client App Image v0.6.2-alpha (Docker Hub)](https://hub.docker.com/r/recksato/robosats-client/tags?page=1&name=v0.6.2-alpha) ```bash -docker pull recksato/robosats-client:v0.6.0-alpha +docker pull recksato/robosats-client:v0.6.2-alpha ``` See [nodeapp/docker-compose.yml](https://github.com/Reckless-Satoshi/robosats/blob/2cd9d748706a8dcc0f03006b483acc6000e0572a/nodeapp/docker-compose.yml) for an example docker-compose usage of the `robosats-client` image. diff --git a/requirements.txt b/requirements.txt index acb9eb8d..84cdbc29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django==5.0.4 +django==5.0.6 django-admin-relation-links==0.2.5 django-celery-beat==2.6.0 django-celery-results==2.5.1 @@ -19,10 +19,11 @@ ring==0.10.1 gunicorn==22.0.0 psycopg2==2.9.9 SQLAlchemy==2.0.16 -django-import-export==3.3.8 +django-import-export==4.0.7 requests[socks] +shapely==2.0.4 python-gnupg==0.5.2 -daphne==4.1.0 +daphne==4.1.2 drf-spectacular==0.27.2 drf-spectacular-sidecar==2024.4.1 django-cors-headers==4.3.1 diff --git a/requirements_dev.txt b/requirements_dev.txt index 1783a603..39535d27 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ -coverage==7.4.4 -ruff==0.3.4 -drf-openapi-tester==2.3.3 +coverage==7.5.0 +ruff==0.4.2 +git+https://github.com/Reckless-Satoshi/drf-openapi-tester.git@soften-django-requirements pre-commit==3.7.0 \ No newline at end of file diff --git a/robosats/middleware.py b/robosats/middleware.py index 47f9bf2a..7e53ff1f 100644 --- a/robosats/middleware.py +++ b/robosats/middleware.py @@ -6,6 +6,7 @@ from channels.middleware import BaseMiddleware from django.contrib.auth.models import AnonymousUser, User, update_last_login from django.utils import timezone from django.utils.deprecation import MiddlewareMixin +from django.http import JsonResponse from rest_framework.authtoken.models import Token from rest_framework.exceptions import AuthenticationFailed @@ -73,8 +74,11 @@ class RobotTokenSHA256AuthenticationMiddleWare: return response if not is_valid_token(token_sha256_b91): - raise AuthenticationFailed( - "Robot token SHA256 was provided in the header. However it is not a valid 39 or 40 characters Base91 string." + return JsonResponse( + { + "bad_request": "Robot token SHA256 was provided in the header. However it is not a valid 39 or 40 characters Base91 string." + }, + status=400, ) # Check if it is an existing robot. @@ -117,8 +121,11 @@ class RobotTokenSHA256AuthenticationMiddleWare: encrypted_private_key = request.COOKIES.get("encrypted_private_key", "") if not public_key or not encrypted_private_key: - raise AuthenticationFailed( - "On the first request to a RoboSats coordinator, you must provide as well a valid public and encrypted private PGP keys" + return JsonResponse( + { + "bad_request": "On the first request to a RoboSats coordinator, you must provide as well a valid public and encrypted private PGP keys" + }, + status=400, ) ( valid, @@ -127,7 +134,7 @@ class RobotTokenSHA256AuthenticationMiddleWare: encrypted_private_key, ) = validate_pgp_keys(public_key, encrypted_private_key) if not valid: - raise AuthenticationFailed(bad_keys_context) + return JsonResponse({"bad_request": bad_keys_context}, status=400) # Hash the token_sha256, only 1 iteration. # This is the second SHA256 of the user token, aka RoboSats ID diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..35ecf8b3 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,7 @@ +# Run e2e tests + +``` +docker compose -f docker-tests.yml --env-file tests/compose.env up -d +docker exec coordinator coverage run manage.py test +docker exec coordinator coverage report +``` \ No newline at end of file diff --git a/tests/api_specs.yaml b/tests/api_specs.yaml deleted file mode 100644 index 3599b2d4..00000000 --- a/tests/api_specs.yaml +++ /dev/null @@ -1,2013 +0,0 @@ -openapi: 3.0.3 -info: - title: RoboSats REST API - version: 0.5.3 - x-logo: - url: https://raw.githubusercontent.com/Reckless-Satoshi/robosats/main/frontend/static/assets/images/robosats-0.1.1-banner.png - backgroundColor: '#FFFFFF' - altText: RoboSats logo - description: |2+ - - REST API Documentation for [RoboSats](https://learn.robosats.com) - A Simple and Private LN P2P Exchange - -

- Note: - The RoboSats REST API is on v0, which in other words, is beta. - We recommend that if you don't have time to actively maintain - your project, do not build it with v0 of the API. A refactored, simpler - and more stable version - v1 will be released soon™. -

- -paths: - /api/book/: - get: - operationId: book_list - description: Get public orders in the book. - summary: Get public orders - parameters: - - in: query - name: currency - schema: - type: integer - description: The currency id to filter by. Currency IDs can be found [here](https://github.com/RoboSats/robosats/blob/main/frontend/static/assets/currencies.json). - Value of `0` means ANY currency - - in: query - name: type - schema: - type: integer - enum: - - 0 - - 1 - - 2 - description: |- - Order type to filter by - - `0` - BUY - - `1` - SELL - - `2` - ALL - tags: - - book - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/OrderPublic' - description: '' - /api/chat/: - get: - operationId: chat_retrieve - description: Returns chat messages for an order with an index higher than `offset`. - tags: - - chat - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/PostMessage' - description: '' - post: - operationId: chat_create - description: Adds one new message to the chatroom. - tags: - - chat - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PostMessage' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/PostMessage' - multipart/form-data: - schema: - $ref: '#/components/schemas/PostMessage' - required: true - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/PostMessage' - description: '' - /api/historical/: - get: - operationId: historical_list - description: Get historical exchange activity. Currently, it lists each day's - total contracts and their volume in BTC since inception. - summary: Get historical exchange activity - tags: - - historical - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - type: array - items: - type: object - additionalProperties: - type: object - properties: - volume: - type: integer - description: Total Volume traded on that particular date - num_contracts: - type: number - description: Number of successful trades on that particular - date - examples: - TruncatedExample: - value: - - : - code: USD - price: '42069.69' - min_amount: '4.2' - max_amount: '420.69' - summary: Truncated example - description: '' - /api/info/: - get: - operationId: info_retrieve - description: |2 - - Get general info (overview) about the exchange. - - **Info**: - - Current market data - - num. of orders - - book liquidity - - 24h active robots - - 24h non-KYC premium - - 24h volume - - all time volume - - Node info - - lnd version - - node id - - node alias - - network - - Fees - - maker and taker fees - - on-chain swap fees - summary: Get info - tags: - - info - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/Info' - description: '' - /api/limits/: - get: - operationId: limits_list - description: Get a list of order limits for every currency pair available. - summary: List order limits - tags: - - limits - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - type: array - items: - type: object - additionalProperties: - type: object - properties: - code: - type: string - description: Three letter currency symbol - price: - type: integer - min_amount: - type: integer - description: Minimum amount allowed in an order in the particular - currency - max_amount: - type: integer - description: Maximum amount allowed in an order in the particular - currency - examples: - TruncatedExample.RealResponseContainsAllTheCurrencies: - value: - - : - code: USD - price: '42069.69' - min_amount: '4.2' - max_amount: '420.69' - summary: Truncated example. Real response contains all the currencies - description: '' - /api/make/: - post: - operationId: make_create - description: |2 - - Create a new order as a maker. - - - Default values for the following fields if not specified: - - `public_duration` - **24** - - `escrow_duration` - **180** - - `bond_size` - **3.0** - - `has_range` - **false** - - `premium` - **0** - summary: Create a maker order - tags: - - make - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/MakeOrder' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/MakeOrder' - multipart/form-data: - schema: - $ref: '#/components/schemas/MakeOrder' - required: true - security: - - tokenAuth: [] - responses: - '201': - content: - application/json: - schema: - $ref: '#/components/schemas/ListOrder' - description: '' - '400': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - description: '' - '409': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - description: '' - /api/order/: - get: - operationId: order_retrieve - description: |2+ - - Get the order details. Details include/exclude attributes according to what is the status of the order - - The following fields are available irrespective of whether you are a participant or not (A participant is either a taker or a maker of an order) - All the other fields are only available when you are either the taker or the maker of the order: - - - `id` - - `status` - - `created_at` - - `expires_at` - - `type` - - `currency` - - `amount` - - `has_range` - - `min_amount` - - `max_amount` - - `payment_method` - - `is_explicit` - - `premium` - - `satoshis` - - `maker` - - `taker` - - `escrow_duration` - - `total_secs_exp` - - `penalty` - - `is_maker` - - `is_taker` - - `is_participant` - - `maker_status` - - `taker_status` - - `price_now` - - ### Order Status - - The response of this route changes according to the status of the order. Some fields are documented below (check the 'Responses' section) - with the status code of when they are available and some or not. With v1 API we aim to simplify this - route to make it easier to understand which fields are available on which order status codes. - - `status` specifies the status of the order. Below is a list of possible values (status codes) and what they mean: - - `0` "Waiting for maker bond" - - `1` "Public" - - `2` "Paused" - - `3` "Waiting for taker bond" - - `4` "Cancelled" - - `5` "Expired" - - `6` "Waiting for trade collateral and buyer invoice" - - `7` "Waiting only for seller trade collateral" - - `8` "Waiting only for buyer invoice" - - `9` "Sending fiat - In chatroom" - - `10` "Fiat sent - In chatroom" - - `11` "In dispute" - - `12` "Collaboratively cancelled" - - `13` "Sending satoshis to buyer" - - `14` "Sucessful trade" - - `15` "Failed lightning network routing" - - `16` "Wait for dispute resolution" - - `17` "Maker lost dispute" - - `18` "Taker lost dispute" - - - Notes: - - both `price_now` and `premium_now` are always calculated irrespective of whether `is_explicit` = true or false - - summary: Get order details - parameters: - - in: query - name: order_id - schema: - type: integer - required: true - tags: - - order - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/OrderDetail' - description: '' - '400': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - examples: - OrderCancelled: - value: - bad_request: This order has been cancelled collaborativelly - summary: Order cancelled - WhenTheOrderIsNotPublicAndYouNeitherTheTakerNorMaker: - value: - bad_request: This order is not available - summary: When the order is not public and you neither the taker - nor maker - WhenMakerBondExpires(asMaker): - value: - bad_request: Invoice expired. You did not confirm publishing the - order in time. Make a new order. - summary: When maker bond expires (as maker) - WhenRobosatsNodeIsDown: - value: - bad_request: The Lightning Network Daemon (LND) is down. Write - in the Telegram group to make sure the staff is aware. - summary: When Robosats node is down - description: '' - '403': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - default: This order is not available - description: '' - '404': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - default: Invalid order Id - description: '' - post: - operationId: order_create - description: |2+ - - Update an order - - `action` field is required and determines what is to be done. Below - is an explanation of what each action does: - - - `take` - - If the order has not expired and is still public, on a - successful take, you get the same response as if `GET /order` - was called and the status of the order was `3` (waiting for - taker bond) which means `bond_satoshis` and `bond_invoice` are - present in the response as well. Once the `bond_invoice` is - paid, you successfully become the taker of the order and the - status of the order changes. - - `pause` - - Toggle the status of an order from `1` to `2` and vice versa. Allowed only if status is `1` (Public) or `2` (Paused) - - `update_invoice` - - This action only is valid if you are the buyer. The `invoice` - field needs to be present in the body and the value must be a - valid LN invoice as cleartext PGP message signed with the robot key. Make sure to perform this action only when - both the bonds are locked. i.e The status of your order is - at least `6` (Waiting for trade collateral and buyer invoice) - - `update_address` - - This action is only valid if you are the buyer. This action is - used to set an on-chain payout address if you wish to have your - payout be received on-chain. Only valid if there is an address in the body as - cleartext PGP message signed with the robot key. This enables on-chain swap for the - order, so even if you earlier had submitted a LN invoice, it - will be ignored. You get to choose the `mining_fee_rate` as - well. Mining fee rate is specified in sats/vbyte. - - `cancel` - - This action is used to cancel an existing order. You cannot cancel an order if it's in one of the following states: - - `1` - Cancelled - - `5` - Expired - - `11` - In dispute - - `12` - Collaboratively cancelled - - `13` - Sending satoshis to buyer - - `14` - Successful trade - - `15` - Failed lightning network routing - - `17` - Maker lost dispute - - `18` - Taker lost dispute - - Note that there are penalties involved for cancelling a order - mid-trade so use this action carefully: - - - As a maker if you cancel an order after you have locked your - maker bond, you are returned your bond. This may change in - the future to prevent DDoSing the LN node and you won't be - returned the maker bond. - - As a taker there is a time penalty involved if you `take` an - order and cancel it without locking the taker bond. - - For both taker or maker, if you cancel the order when both - have locked their bonds (status = `6` or `7`), you loose your - bond and a percent of it goes as "rewards" to your - counterparty and some of it the platform keeps. This is to - discourage wasting time and DDoSing the platform. - - For both taker or maker, if you cancel the order when the - escrow is locked (status = `8` or `9`), you trigger a - collaborative cancel request. This sets - `(m|t)aker_asked_cancel` field to `true` depending on whether - you are the maker or the taker respectively, so that your - counterparty is informed that you asked for a cancel. - - For both taker or maker, and your counterparty asked for a - cancel (i.e `(m|t)aker_asked_cancel` is true), and you cancel - as well, a collaborative cancel takes place which returns - both the bonds and escrow to the respective parties. Note - that in the future there will be a cost for even - collaborativelly cancelling orders for both parties. - - `confirm` - - This is a **crucial** action. This confirms the sending and - receiving of fiat depending on whether you are a buyer or - seller. There is not much RoboSats can do to actually confirm - and verify the fiat payment channel. It is up to you to make - sure of the correct amount was received before you confirm. - This action is only allowed when status is either `9` (Sending - fiat - In chatroom) or `10` (Fiat sent - In chatroom) - - If you are the buyer, it simply sets `fiat_sent` to `true` - which means that you have sent the fiat using the payment - method selected by the seller and signals the seller that the - fiat payment was done. - - If you are the seller, be very careful and double check - before performing this action. Check that your fiat payment - method was successful in receiving the funds and whether it - was the correct amount. This action settles the escrow and - pays the buyer and sets the the order status to `13` (Sending - satohis to buyer) and eventually to `14` (successful trade). - - `undo_confirm` - - This action will undo the fiat_sent confirmation by the buyer - it is allowed only once the fiat is confirmed as sent and can - enable the collaborative cancellation option if an off-robosats - payment cannot be completed or is blocked. - - `dispute` - - This action is allowed only if status is `9` or `10`. It sets - the order status to `11` (In dispute) and sets `is_disputed` to - `true`. Both the bonds and the escrow are settled (i.e RoboSats - takes custody of the funds). Disputes can take long to resolve, - it might trigger force closure for unresolved HTLCs). Dispute - winner will have to submit a new invoice for value of escrow + - bond. - - `submit_statement` - - This action updates the dispute statement. Allowed only when - status is `11` (In dispute). `statement` must be sent in the - request body and should be a string. 100 chars < length of - `statement` < 5000 chars. You need to describe the reason for - raising a dispute. The `(m|t)aker_statement` field is set - respectively. Only when both parties have submitted their - dispute statement, the order status changes to `16` (Waiting - for dispute resolution) - - `rate_platform` - - Let us know how much you love (or hate 😢) RoboSats. - You can rate the platform from `1-5` using the `rate` field in the request body - - summary: Update order - parameters: - - in: query - name: order_id - schema: - type: integer - required: true - tags: - - order - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateOrder' - examples: - UserNotAuthenticated: - value: - bad_request: Woops! It seems you do not have a robot avatar - summary: User not authenticated - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/UpdateOrder' - multipart/form-data: - schema: - $ref: '#/components/schemas/UpdateOrder' - required: true - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/OrderDetail' - description: '' - '400': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - examples: - UserNotAuthenticated: - value: - bad_request: Woops! It seems you do not have a robot avatar - summary: User not authenticated - description: '' - /api/price/: - get: - operationId: price_list - description: Get the last market price for each currency. Also, returns some - more info about the last trade in each currency. - summary: Get last market prices - tags: - - price - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - type: array - items: - type: object - additionalProperties: - type: object - properties: - price: - type: integer - volume: - type: integer - premium: - type: integer - timestamp: - type: string - format: date-time - examples: - TruncatedExample.RealResponseContainsAllTheCurrencies: - value: - - : - price: 21948.89 - volume: 0.01366812 - premium: 3.5 - timestamp: '2022-09-13T14:32:40.591774Z' - summary: Truncated example. Real response contains all the currencies - description: '' - /api/reward/: - post: - operationId: reward_create - description: Withdraw user reward by submitting an invoice. The invoice must - be send as cleartext PGP message signed with the robot key - summary: Withdraw reward - tags: - - reward - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/ClaimReward' - examples: - UserNotAuthenticated: - value: - bad_request: Woops! It seems you do not have a robot avatar - summary: User not authenticated - WhenNoRewardsEarned: - value: - successful_withdrawal: false - bad_invoice: You have not earned rewards - summary: When no rewards earned - BadInvoiceOrInCaseOfPaymentFailure: - value: - successful_withdrawal: false - bad_invoice: Does not look like a valid lightning invoice - summary: Bad invoice or in case of payment failure - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/ClaimReward' - multipart/form-data: - schema: - $ref: '#/components/schemas/ClaimReward' - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - type: object - properties: - successful_withdrawal: - type: boolean - default: true - description: '' - '400': - content: - application/json: - schema: - oneOf: - - type: object - properties: - successful_withdrawal: - type: boolean - default: false - bad_invoice: - type: string - description: More context for the reason of the failure - - type: object - properties: - successful_withdrawal: - type: boolean - default: false - bad_request: - type: string - description: More context for the reason of the failure - examples: - UserNotAuthenticated: - value: - bad_request: Woops! It seems you do not have a robot avatar - summary: User not authenticated - WhenNoRewardsEarned: - value: - successful_withdrawal: false - bad_invoice: You have not earned rewards - summary: When no rewards earned - BadInvoiceOrInCaseOfPaymentFailure: - value: - successful_withdrawal: false - bad_invoice: Does not look like a valid lightning invoice - summary: Bad invoice or in case of payment failure - description: '' - /api/robot/: - get: - operationId: robot_retrieve - description: |2+ - - Get robot info 🤖 - - An authenticated request (has the token's sha256 hash encoded as base 91 in the Authorization header) will be - returned the information about the state of a robot. - - Make sure you generate your token using cryptographically secure methods. [Here's]() the function the Javascript - client uses to generate the tokens. Since the server only receives the hash of the - token, it is responsibility of the client to create a strong token. Check - [here](https://github.com/RoboSats/robosats/blob/main/frontend/src/utils/token.js) - to see how the Javascript client creates a random strong token and how it validates entropy is optimal for tokens - created by the user at will. - - `public_key` - PGP key associated with the user (Armored ASCII format) - `encrypted_private_key` - Private PGP key. This is only stored on the backend for later fetching by - the frontend and the key can't really be used by the server since it's protected by the token - that only the client knows. Will be made an optional parameter in a future release. - On the Javascript client, It's passphrase is set to be the secret token generated. - - A gpg key can be created by: - - ```shell - gpg --full-gen-key - ``` - - it's public key can be exported in ascii armored format with: - - ```shell - gpg --export --armor - ``` - - and it's private key can be exported in ascii armored format with: - - ```shell - gpg --export-secret-keys --armor - ``` - - summary: Get robot info - tags: - - robot - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - type: object - properties: - encrypted_private_key: - type: string - description: Armored ASCII PGP private key block - nickname: - type: string - description: Username generated (Robot name) - public_key: - type: string - description: Armored ASCII PGP public key block - wants_stealth: - type: boolean - default: false - description: Whether the user prefers stealth invoices - found: - type: boolean - description: Robot had been created in the past. Only if the robot - was created +5 mins ago. - tg_enabled: - type: boolean - description: The robot has telegram notifications enabled - tg_token: - type: string - description: Token to enable telegram with /start - tg_bot_name: - type: string - description: Name of the coordinator's telegram bot - active_order_id: - type: integer - description: Active order id if present - last_order_id: - type: integer - description: Last order id if present - earned_rewards: - type: integer - description: Satoshis available to be claimed - last_login: - type: string - format: date-time - nullable: true - description: Last time the coordinator saw this robot - examples: - SuccessfullyRetrievedRobot: - value: - nickname: SatoshiNakamoto21 - public_key: |- - -----BEGIN PGP PUBLIC KEY BLOCK----- - - ...... - ...... - encrypted_private_key: |- - -----BEGIN PGP PRIVATE KEY BLOCK----- - - ...... - ...... - wants_stealth: true - summary: Successfully retrieved robot - description: '' - /api/stealth/: - post: - operationId: stealth_create - description: Update stealth invoice option for the user - summary: Update stealth option - tags: - - stealth - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Stealth' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Stealth' - multipart/form-data: - schema: - $ref: '#/components/schemas/Stealth' - required: true - security: - - tokenAuth: [] - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/Stealth' - description: '' - '400': - content: - application/json: - schema: - type: object - properties: - bad_request: - type: string - description: Reason for the failure - description: '' - /api/ticks/: - get: - operationId: ticks_list - description: |- - Get all market ticks. Returns a list of all the market ticks since inception. - CEX price is also recorded for useful insight on the historical premium of Non-KYC BTC. Price is set when taker bond is locked. - summary: Get market ticks - parameters: - - in: query - name: end - schema: - type: string - description: End date formatted as DD-MM-YYYY - - in: query - name: start - schema: - type: string - description: Start date formatted as DD-MM-YYYY - tags: - - ticks - security: - - tokenAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Tick' - description: '' -components: - schemas: - ActionEnum: - enum: - - pause - - take - - update_invoice - - update_address - - submit_statement - - dispute - - cancel - - confirm - - undo_confirm - - rate_platform - type: string - description: |- - * `pause` - pause - * `take` - take - * `update_invoice` - update_invoice - * `update_address` - update_address - * `submit_statement` - submit_statement - * `dispute` - dispute - * `cancel` - cancel - * `confirm` - confirm - * `undo_confirm` - undo_confirm - * `rate_platform` - rate_platform - BlankEnum: - enum: - - '' - ClaimReward: - type: object - properties: - invoice: - type: string - nullable: true - description: A valid LN invoice with the reward amount to withdraw - maxLength: 2000 - CurrencyEnum: - enum: - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - 10 - - 11 - - 12 - - 13 - - 14 - - 15 - - 16 - - 17 - - 18 - - 19 - - 20 - - 21 - - 22 - - 23 - - 24 - - 25 - - 26 - - 27 - - 28 - - 29 - - 30 - - 31 - - 32 - - 33 - - 34 - - 35 - - 36 - - 37 - - 38 - - 39 - - 40 - - 41 - - 42 - - 43 - - 44 - - 45 - - 46 - - 47 - - 48 - - 49 - - 50 - - 51 - - 52 - - 53 - - 54 - - 55 - - 56 - - 57 - - 58 - - 59 - - 60 - - 61 - - 62 - - 63 - - 64 - - 65 - - 66 - - 67 - - 68 - - 69 - - 70 - - 71 - - 72 - - 73 - - 74 - - 75 - - 300 - - 1000 - type: integer - description: |- - * `1` - USD - * `2` - EUR - * `3` - JPY - * `4` - GBP - * `5` - AUD - * `6` - CAD - * `7` - CHF - * `8` - CNY - * `9` - HKD - * `10` - NZD - * `11` - SEK - * `12` - KRW - * `13` - SGD - * `14` - NOK - * `15` - MXN - * `16` - BYN - * `17` - RUB - * `18` - ZAR - * `19` - TRY - * `20` - BRL - * `21` - CLP - * `22` - CZK - * `23` - DKK - * `24` - HRK - * `25` - HUF - * `26` - INR - * `27` - ISK - * `28` - PLN - * `29` - RON - * `30` - ARS - * `31` - VES - * `32` - COP - * `33` - PEN - * `34` - UYU - * `35` - PYG - * `36` - BOB - * `37` - IDR - * `38` - ANG - * `39` - CRC - * `40` - CUP - * `41` - DOP - * `42` - GHS - * `43` - GTQ - * `44` - ILS - * `45` - JMD - * `46` - KES - * `47` - KZT - * `48` - MYR - * `49` - NAD - * `50` - NGN - * `51` - AZN - * `52` - PAB - * `53` - PHP - * `54` - PKR - * `55` - QAR - * `56` - SAR - * `57` - THB - * `58` - TTD - * `59` - VND - * `60` - XOF - * `61` - TWD - * `62` - TZS - * `63` - XAF - * `64` - UAH - * `65` - EGP - * `66` - LKR - * `67` - MAD - * `68` - AED - * `69` - TND - * `70` - ETB - * `71` - GEL - * `72` - UGX - * `73` - RSD - * `74` - IRT - * `75` - BDT - * `300` - XAU - * `1000` - BTC - ExpiryReasonEnum: - enum: - - 0 - - 1 - - 2 - - 3 - - 4 - type: integer - description: |- - * `0` - Expired not taken - * `1` - Maker bond not locked - * `2` - Escrow not locked - * `3` - Invoice not submitted - * `4` - Neither escrow locked or invoice submitted - Info: - type: object - properties: - num_public_buy_orders: - type: integer - num_public_sell_orders: - type: integer - book_liquidity: - type: integer - description: Total amount of BTC in the order book - active_robots_today: - type: integer - last_day_nonkyc_btc_premium: - type: number - format: double - description: Average premium (weighted by volume) of the orders in the last - 24h - last_day_volume: - type: number - format: double - description: Total volume in BTC in the last 24h - lifetime_volume: - type: number - format: double - description: Total volume in BTC since exchange's inception - lnd_version: - type: string - cln_version: - type: string - robosats_running_commit_hash: - type: string - alternative_site: - type: string - alternative_name: - type: string - node_alias: - type: string - node_id: - type: string - network: - type: string - maker_fee: - type: number - format: double - description: Exchange's set maker fee - taker_fee: - type: number - format: double - description: 'Exchange''s set taker fee ' - bond_size: - type: number - format: double - description: Default bond size (percent) - current_swap_fee_rate: - type: number - format: double - description: Swap fees to perform on-chain transaction (percent) - version: - $ref: '#/components/schemas/Version' - notice_severity: - $ref: '#/components/schemas/NoticeSeverityEnum' - notice_message: - type: string - required: - - active_robots_today - - alternative_name - - alternative_site - - bond_size - - book_liquidity - - cln_version - - current_swap_fee_rate - - last_day_nonkyc_btc_premium - - last_day_volume - - lifetime_volume - - lnd_version - - maker_fee - - network - - node_alias - - node_id - - notice_message - - notice_severity - - num_public_buy_orders - - num_public_sell_orders - - robosats_running_commit_hash - - taker_fee - - version - ListOrder: - type: object - properties: - id: - type: integer - readOnly: true - status: - allOf: - - $ref: '#/components/schemas/StatusEnum' - minimum: 0 - maximum: 32767 - created_at: - type: string - format: date-time - expires_at: - type: string - format: date-time - type: - allOf: - - $ref: '#/components/schemas/TypeEnum' - minimum: 0 - maximum: 32767 - currency: - type: integer - nullable: true - amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - has_range: - type: boolean - min_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - max_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - payment_method: - type: string - maxLength: 70 - is_explicit: - type: boolean - premium: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ - nullable: true - satoshis: - type: integer - maximum: 5000000 - minimum: 20000 - nullable: true - maker: - type: integer - nullable: true - taker: - type: integer - nullable: true - escrow_duration: - type: integer - maximum: 28800 - minimum: 1800 - bond_size: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,2})?$ - latitude: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,6})?$ - nullable: true - longitude: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,6})?$ - nullable: true - required: - - expires_at - - id - - type - MakeOrder: - type: object - properties: - type: - allOf: - - $ref: '#/components/schemas/TypeEnum' - minimum: 0 - maximum: 32767 - currency: - type: integer - description: Currency id. See [here](https://github.com/RoboSats/robosats/blob/main/frontend/static/assets/currencies.json) - for a list of all IDs - amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - has_range: - type: boolean - default: false - description: |- - Whether the order specifies a range of amount or a fixed amount. - - If `true`, then `min_amount` and `max_amount` fields are **required**. - - If `false` then `amount` is **required** - min_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - max_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - payment_method: - type: string - default: not specified - description: Can be any string. The UI recognizes [these payment methods](https://github.com/RoboSats/robosats/blob/main/frontend/src/components/payment-methods/Methods.js) - and displays them with a logo. - maxLength: 70 - is_explicit: - type: boolean - default: false - description: Whether the order is explicitly priced or not. If set to `true` - then `satoshis` need to be specified - premium: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ - nullable: true - satoshis: - type: integer - maximum: 5000000 - minimum: 20000 - nullable: true - public_duration: - type: integer - maximum: 86400 - minimum: 597.6 - escrow_duration: - type: integer - maximum: 28800 - minimum: 1800 - bond_size: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,2})?$ - latitude: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,6})?$ - nullable: true - longitude: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,6})?$ - nullable: true - required: - - currency - - type - Nested: - type: object - properties: - id: - type: integer - readOnly: true - currency: - allOf: - - $ref: '#/components/schemas/CurrencyEnum' - minimum: 0 - maximum: 32767 - exchange_rate: - type: string - format: decimal - pattern: ^-?\d{0,14}(?:\.\d{0,4})?$ - nullable: true - timestamp: - type: string - format: date-time - required: - - currency - - id - NoticeSeverityEnum: - enum: - - none - - warning - - success - - error - - info - type: string - description: |- - * `none` - none - * `warning` - warning - * `success` - success - * `error` - error - * `info` - info - NullEnum: - enum: - - null - OrderDetail: - type: object - properties: - id: - type: integer - readOnly: true - status: - allOf: - - $ref: '#/components/schemas/StatusEnum' - minimum: 0 - maximum: 32767 - created_at: - type: string - format: date-time - expires_at: - type: string - format: date-time - type: - allOf: - - $ref: '#/components/schemas/TypeEnum' - minimum: 0 - maximum: 32767 - currency: - type: integer - nullable: true - amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - has_range: - type: boolean - min_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - max_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - payment_method: - type: string - maxLength: 70 - is_explicit: - type: boolean - premium: - type: string - description: Premium over the CEX price set by the maker - premium_now: - type: number - format: double - description: Premium over the CEX price at the current time - satoshis: - type: integer - maximum: 5000000 - minimum: 20000 - nullable: true - satoshis_now: - type: integer - description: Maximum size of the order right now in Satoshis - maker: - type: integer - nullable: true - taker: - type: integer - nullable: true - escrow_duration: - type: integer - maximum: 28800 - minimum: 1800 - total_secs_exp: - type: integer - description: Duration of time (in seconds) to expire, according to the current - status of order.This is duration of time after `created_at` (in seconds) - that the order will automatically expire.This value changes according - to which stage the order is in - penalty: - type: string - format: date-time - description: Time when the user penalty will expire. Penalty applies when - you create orders repeatedly without commiting a bond - is_maker: - type: boolean - description: Whether you are the maker or not - is_taker: - type: boolean - description: Whether you are the taker or not - is_participant: - type: boolean - description: True if you are either a taker or maker, False otherwise - maker_status: - type: string - description: |- - Status of the maker: - - **'Active'** (seen within last 2 min) - - **'Seen Recently'** (seen within last 10 min) - - **'Inactive'** (seen more than 10 min ago) - - Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty - taker_status: - type: string - description: |- - Status of the maker: - - **'Active'** (seen within last 2 min) - - **'Seen Recently'** (seen within last 10 min) - - **'Inactive'** (seen more than 10 min ago) - - Note: When you make a request to this route, your own status get's updated and can be seen by your counterparty - price_now: - type: number - format: double - description: Price of the order in the order's currency at the time of request - (upto 5 significant digits) - premium_percentile: - type: number - format: double - description: (Only if `is_maker`) Premium percentile of your order compared - to other public orders in the same currency currently in the order book - num_similar_orders: - type: integer - description: (Only if `is_maker`) The number of public orders of the same - currency currently in the order book - tg_enabled: - type: boolean - description: (Only if `is_maker`) Whether Telegram notification is enabled - or not - tg_token: - type: string - description: (Only if `is_maker`) Your telegram bot token required to enable - notifications. - tg_bot_name: - type: string - description: (Only if `is_maker`) The Telegram username of the bot - is_buyer: - type: boolean - description: Whether you are a buyer of sats (you will be receiving sats) - is_seller: - type: boolean - description: Whether you are a seller of sats or not (you will be sending - sats) - maker_nick: - type: string - description: Nickname (Robot name) of the maker - taker_nick: - type: string - description: Nickname (Robot name) of the taker - status_message: - type: string - description: The current status of the order corresponding to the `status` - is_fiat_sent: - type: boolean - description: Whether or not the fiat amount is sent by the buyer - is_disputed: - type: boolean - description: Whether or not the counterparty raised a dispute - ur_nick: - type: string - description: Your Nick - maker_locked: - type: boolean - description: True if maker bond is locked, False otherwise - taker_locked: - type: boolean - description: True if taker bond is locked, False otherwise - escrow_locked: - type: boolean - description: True if escrow is locked, False otherwise. Escrow is the sats - to be sold, held by Robosats until the trade is finised. - trade_satoshis: - type: integer - description: 'Seller sees the amount of sats they need to send. Buyer sees - the amount of sats they will receive ' - bond_invoice: - type: string - description: When `status` = `0`, `3`. Bond invoice to be paid - bond_satoshis: - type: integer - description: The bond amount in satoshis - escrow_invoice: - type: string - description: For the seller, the escrow invoice to be held by RoboSats - escrow_satoshis: - type: integer - description: The escrow amount in satoshis - invoice_amount: - type: integer - description: The amount in sats the buyer needs to submit an invoice of - to receive the trade amount - swap_allowed: - type: boolean - description: Whether on-chain swap is allowed - swap_failure_reason: - type: string - description: Reason for why on-chain swap is not available - suggested_mining_fee_rate: - type: integer - description: fee in sats/vbyte for the on-chain swap - swap_fee_rate: - type: number - format: double - description: in percentage, the swap fee rate the platform charges - pending_cancel: - type: boolean - description: Your counterparty requested for a collaborative cancel when - `status` is either `8`, `9` or `10` - asked_for_cancel: - type: boolean - description: You requested for a collaborative cancel `status` is either - `8`, `9` or `10` - statement_submitted: - type: boolean - description: True if you have submitted a statement. Available when `status` - is `11` - retries: - type: integer - description: Number of times ln node has tried to make the payment to you - (only if you are the buyer) - next_retry_time: - type: string - format: date-time - description: The next time payment will be retried. Payment is retried every - 1 sec - failure_reason: - type: string - description: The reason the payout failed - invoice_expired: - type: boolean - description: True if the payout invoice expired. `invoice_amount` will be - re-set and sent which means the user has to submit a new invoice to be - payed - public_duration: - type: integer - maximum: 86400 - minimum: 597.6 - bond_size: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,2})?$ - trade_fee_percent: - type: integer - description: The fee for the trade (fees differ for maker and taker) - bond_size_sats: - type: integer - description: The size of the bond in sats - bond_size_percent: - type: integer - description: same as `bond_size` - maker_summary: - $ref: '#/components/schemas/Summary' - taker_summary: - $ref: '#/components/schemas/Summary' - platform_summary: - $ref: '#/components/schemas/PlatformSummary' - expiry_reason: - nullable: true - minimum: 0 - maximum: 32767 - oneOf: - - $ref: '#/components/schemas/ExpiryReasonEnum' - - $ref: '#/components/schemas/NullEnum' - expiry_message: - type: string - description: The reason the order expired (message associated with the `expiry_reason`) - num_satoshis: - type: integer - description: only if status = `14` (Successful Trade) and is_buyer = `true` - sent_satoshis: - type: integer - description: only if status = `14` (Successful Trade) and is_buyer = `true` - txid: - type: string - description: Transaction id of the on-chain swap payout. Only if status - = `14` (Successful Trade) and is_buyer = `true` - network: - type: string - description: The network eg. 'testnet', 'mainnet'. Only if status = `14` - (Successful Trade) and is_buyer = `true` - latitude: - type: number - format: double - description: Latitude of the order for F2F payments - longitude: - type: number - format: double - description: Longitude of the order for F2F payments - required: - - expires_at - - id - - type - OrderPublic: - type: object - properties: - id: - type: integer - readOnly: true - created_at: - type: string - format: date-time - expires_at: - type: string - format: date-time - type: - allOf: - - $ref: '#/components/schemas/TypeEnum' - minimum: 0 - maximum: 32767 - currency: - type: integer - nullable: true - amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - has_range: - type: boolean - min_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - max_amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - payment_method: - type: string - maxLength: 70 - is_explicit: - type: boolean - premium: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ - nullable: true - satoshis: - type: integer - maximum: 5000000 - minimum: 20000 - nullable: true - maker: - type: integer - nullable: true - maker_nick: - type: string - maker_status: - type: string - description: Status of the nick - "Active" or "Inactive" - price: - type: number - format: double - description: Price in order's fiat currency - escrow_duration: - type: integer - maximum: 28800 - minimum: 1800 - satoshis_now: - type: integer - description: The amount of sats to be traded at the present moment (not - including the fees) - bond_size: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,2})?$ - latitude: - type: string - format: decimal - pattern: ^-?\d{0,2}(?:\.\d{0,6})?$ - nullable: true - longitude: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,6})?$ - nullable: true - required: - - expires_at - - id - - type - PlatformSummary: - type: object - properties: - contract_timestamp: - type: string - format: date-time - description: Timestamp of when the contract was finalized (price and sats - fixed) - contract_total_time: - type: number - format: double - description: The time taken for the contract to complete (from taker taking - the order to completion of order) in seconds - routing_fee_sats: - type: integer - description: Sats payed by the exchange for routing fees. Mining fee in - case of on-chain swap payout - trade_revenue_sats: - type: integer - description: The sats the exchange earned from the trade - PostMessage: - type: object - properties: - PGP_message: - type: string - nullable: true - maxLength: 5000 - order_id: - type: integer - minimum: 0 - description: Order ID of chatroom - offset: - type: integer - minimum: 0 - nullable: true - description: Offset for message index to get as response - required: - - order_id - RatingEnum: - enum: - - '1' - - '2' - - '3' - - '4' - - '5' - type: string - description: |- - * `1` - 1 - * `2` - 2 - * `3` - 3 - * `4` - 4 - * `5` - 5 - StatusEnum: - enum: - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - 10 - - 11 - - 12 - - 13 - - 14 - - 15 - - 16 - - 17 - - 18 - type: integer - description: |- - * `0` - Waiting for maker bond - * `1` - Public - * `2` - Paused - * `3` - Waiting for taker bond - * `4` - Cancelled - * `5` - Expired - * `6` - Waiting for trade collateral and buyer invoice - * `7` - Waiting only for seller trade collateral - * `8` - Waiting only for buyer invoice - * `9` - Sending fiat - In chatroom - * `10` - Fiat sent - In chatroom - * `11` - In dispute - * `12` - Collaboratively cancelled - * `13` - Sending satoshis to buyer - * `14` - Sucessful trade - * `15` - Failed lightning network routing - * `16` - Wait for dispute resolution - * `17` - Maker lost dispute - * `18` - Taker lost dispute - Stealth: - type: object - properties: - wantsStealth: - type: boolean - required: - - wantsStealth - Summary: - type: object - properties: - sent_fiat: - type: integer - description: same as `amount` (only for buyer) - received_sats: - type: integer - description: same as `trade_satoshis` (only for buyer) - is_swap: - type: boolean - description: True if the payout was on-chain (only for buyer) - received_onchain_sats: - type: integer - description: The on-chain sats received (only for buyer and if `is_swap` - is `true`) - mining_fee_sats: - type: integer - description: Mining fees paid in satoshis (only for buyer and if `is_swap` - is `true`) - swap_fee_sats: - type: integer - description: Exchange swap fee in sats (i.e excluding miner fees) (only - for buyer and if `is_swap` is `true`) - swap_fee_percent: - type: number - format: double - description: same as `swap_fee_rate` (only for buyer and if `is_swap` is - `true` - sent_sats: - type: integer - description: The total sats you sent (only for seller) - received_fiat: - type: integer - description: same as `amount` (only for seller) - trade_fee_sats: - type: integer - description: Exchange fees in sats (Does not include swap fee and miner - fee) - Tick: - type: object - properties: - timestamp: - type: string - format: date-time - currency: - allOf: - - $ref: '#/components/schemas/Nested' - readOnly: true - volume: - type: string - format: decimal - nullable: true - price: - type: string - format: decimal - pattern: ^-?\d{0,14}(?:\.\d{0,2})?$ - nullable: true - premium: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ - nullable: true - fee: - type: string - format: decimal - required: - - currency - TypeEnum: - enum: - - 0 - - 1 - type: integer - description: |- - * `0` - BUY - * `1` - SELL - UpdateOrder: - type: object - properties: - invoice: - type: string - nullable: true - description: |+ - Invoice used for payouts. Must be PGP signed with the robot's public key. The expected Armored PGP header is -----BEGIN PGP SIGNED MESSAGE----- - Hash: SHA512 - - maxLength: 15000 - routing_budget_ppm: - type: integer - maximum: 100001 - minimum: 0 - nullable: true - default: 0 - description: Max budget to allocate for routing in PPM - address: - type: string - nullable: true - description: |+ - Onchain address used for payouts. Must be PGP signed with the robot's public key. The expected Armored PGP header is -----BEGIN PGP SIGNED MESSAGE----- - Hash: SHA512 - - maxLength: 15000 - statement: - type: string - nullable: true - maxLength: 500000 - action: - $ref: '#/components/schemas/ActionEnum' - rating: - nullable: true - oneOf: - - $ref: '#/components/schemas/RatingEnum' - - $ref: '#/components/schemas/BlankEnum' - - $ref: '#/components/schemas/NullEnum' - amount: - type: string - format: decimal - pattern: ^-?\d{0,10}(?:\.\d{0,8})?$ - nullable: true - mining_fee_rate: - type: string - format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,3})?$ - nullable: true - required: - - action - Version: - type: object - properties: - major: - type: integer - minor: - type: integer - patch: - type: integer - required: - - major - - minor - - patch - securitySchemes: - tokenAuth: - type: apiKey - in: header - name: Authorization - description: Token-based authentication with required prefix "Token" diff --git a/tests/test_trade_pipeline.py b/tests/test_trade_pipeline.py index 4205b98c..32a39c9e 100644 --- a/tests/test_trade_pipeline.py +++ b/tests/test_trade_pipeline.py @@ -239,6 +239,38 @@ class TradeTest(BaseAPITestCase): self.assertIsNone(data["taker"], "New order's taker is not null") self.assert_order_logs(data["id"]) + def test_make_order_on_blocked_country(self): + """ + Test the creation of an F2F order on a geoblocked location + """ + trade = Trade( + self.client, + # latitude and longitud in Aruba. One of the countries blocked in the example conf. + maker_form={ + "type": 0, + "currency": 1, + "has_range": True, + "min_amount": 21, + "max_amount": 101.7, + "payment_method": "Advcash Cash F2F", + "is_explicit": False, + "premium": 3.34, + "public_duration": 69360, + "escrow_duration": 8700, + "bond_size": 3.5, + "latitude": -11.8014, # Angola AGO + "longitude": 17.3575, + }, + ) # init of Trade calls make_order() with the default maker form. + data = trade.response.json() + + self.assertEqual(trade.response.status_code, 400) + self.assertResponse(trade.response) + + self.assertEqual( + data["bad_request"], "The coordinator does not support orders in AGO" + ) + def test_get_order_created(self): """ Tests the creation of an order and the first request to see details, diff --git a/tests/utils/trade.py b/tests/utils/trade.py index b00d0ae7..16b9b89d 100644 --- a/tests/utils/trade.py +++ b/tests/utils/trade.py @@ -98,8 +98,8 @@ class Trade: response = self.client.post(path, maker_form, **headers) + self.response = response if response.status_code == 201: - self.response = response self.order_id = response.json()["id"] def get_order(self, robot_index=1, first_encounter=False): diff --git a/version.json b/version.json index d2ed8852..63ef6d75 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { "major": 0, "minor": 6, - "patch": 0 + "patch": 2 }