From bbe4879df921543d6b3e307ae7b0e22ffb985606 Mon Sep 17 00:00:00 2001 From: xsthar Date: Mon, 19 Oct 2026 15:01:57 +0000 Subject: [PATCH 1/4] docs(rebrand): add EziAgric to EliteWork migration note (#126) --- ...026-10-19-rebrand-eziagric-to-elitework.md | 78 +++++++++++++++++++ docs/releases/release-note-template.md | 2 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 docs/releases/2026-10-19-rebrand-eziagric-to-elitework.md diff --git a/docs/releases/2026-10-19-rebrand-eziagric-to-elitework.md b/docs/releases/2026-10-19-rebrand-eziagric-to-elitework.md new file mode 100644 index 00000000..17475a62 --- /dev/null +++ b/docs/releases/2026-10-19-rebrand-eziagric-to-elitework.md @@ -0,0 +1,78 @@ +# Release Note — Rebrand: EziAgric → EliteWork + +> Date: 2026-10-19 +> Type: Docs / brand migration +> Status: Rebrand shipped; residual-reference cleanup tracked below + +## Overview + +The project has been rebranded from **EziAgric** to **EliteWork** across the +product name, documentation, UI copy, and repository references. This note +records what changed, which legacy identifiers are intentionally retained for +compatibility, and the cleanup that is still outstanding so the history is not +lost to git-log archaeology. + +## Feature + +### What changed + +- Brand-facing references now use **EliteWork** instead of **EziAgric**. +- Repository and organisation links point at + [`Elite-Work/EliteWork`](https://github.com/Elite-Work/EliteWork). +- UI copy shown to users refers to EliteWork (for example the Freighter + wrong-network message). +- New documentation follows the EliteWork name; this release note records the + migration itself. + +### Naming map + +| Layer | Current name | Notes | +|---|---|---| +| Product / docs / UI | **EliteWork** | Canonical brand name going forward | +| GitHub organisation / repo | **Elite-Work / EliteWork** | Old `EziAgric` URLs rely on GitHub redirects | +| Backend package / service identifiers | `amana-*`, `Amana*` | Legacy internal codename; not user-facing, left unchanged to avoid a breaking rename | +| On-chain contract crate | `amana_escrow` | Contract/package identifier; renaming would require a deployment/migration | +| Pact consumer/provider names | `AmanaFrontend`, `AmanaBackend` | Contract identifiers; changing them would orphan existing pact files | + +### Known residual references (follow-up cleanup) + +The following references were found during the rebrand audit and are **not** +fixed by this note. They are safe to update incrementally, but each needs its +own review because some are external-facing links or package identifiers. + +| Area | Path / reference | Follow-up | +|---|---|---| +| Top-level README | `README.md` heading and body | Replace remaining EziAgric copy with EliteWork | +| Frontend i18n | `frontend/src/lib/i18n/messages/{en,fr}.ts` | Update wrong-network copy to EliteWork | +| Frontend docs | `frontend/docs/i18n.md` | Update EziAgric references | +| Backend docs | `backend/docs/retry-classification.md`, `backend/docs/health-probe-semantics.md` | Update titles and image references | +| Infra docs | `infra/terraform/modules/{vpc,rds}/README.md` | Update module descriptions | +| Scripts | `scripts/verify-secrets-rotation.sh` | Update banner text | +| Doc references | `docs/admin-route-contribution-guide.md`, `docs/admin-secret-management.md` | Update EziAgric copy | +| External issue links | `docs/storage-layout-gate.md`, `docs/deadline-extension-caps.md`, `docs/mediator-quorum.md`, `docs/slo-review-log.md` | Repoint old `EziAgric/EziAgric` issue links to `Elite-Work/EliteWork` | +| Shared schemas | `docs/shared-schemas.md` | Decide whether `@eziagric/domain-schemas` is renamed; document the package migration first | +| Contract docs | `docs/eziagric_contract_overview.md` and `contracts/amana_escrow/INTERFACE.md` | Rename the overview doc and update inbound links in the same PR | +| Release template | `docs/releases/release-note-template.md` | Updated to EliteWork in this change | + +### External links, bookmarks, and integrations + +- GitHub redirects old `github.com/EziAgric/...` URLs to + `github.com/Elite-Work/EliteWork`, but third-party bookmarks, dashboards, + webhook payloads, and runbook links may still embed the old org/repo path. +- Check alert routing, Grafana links, CI badges, npm/PyPI package pages, and + deployment manifests for hard-coded `EziAgric` URLs before announcing the + rename externally. +- Old bookmarks to `docs/eziagric_contract_overview.md` should be redirected or + kept working until the doc rename ships. + +## Verification + +- [x] Rebrand note stored under `docs/releases/` +- [x] Naming map records which legacy identifiers are intentionally retained +- [x] Residual references listed with actionable follow-ups +- [ ] Residual references above cleaned up in follow-up PRs +- [ ] External bookmarks/links audited by comms/ops + +## Related Issues + +- Closes #126 diff --git a/docs/releases/release-note-template.md b/docs/releases/release-note-template.md index 70276eb8..9ec673aa 100644 --- a/docs/releases/release-note-template.md +++ b/docs/releases/release-note-template.md @@ -1,6 +1,6 @@ # Release Note Template -> Template for producing consistent release notes for new EziAgric features. +> Template for producing consistent release notes for new EliteWork features. > Copy this file into `docs/releases/`, name it `YYYY-MM-DD-.md`, fill in > each section, and delete the instructional comments. Ensure entries exist for > every **admin feature**, **docs**, and **infra** change in the release. From 45f5ffffed61c384537d4e5f6279e4fa7cb260e1 Mon Sep 17 00:00:00 2001 From: xsthar Date: Mon, 19 Oct 2026 15:02:14 +0000 Subject: [PATCH 2/4] docs(runbooks): add Redis outage active-settlement runbook (#129) --- backend/src/config/alertRegistry.ts | 5 +- docs/redis-resilience.md | 9 +- docs/runbooks/redis-maintenance.md | 3 +- .../redis-outage-active-settlement.md | 238 ++++++++++++++++++ 4 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 docs/runbooks/redis-outage-active-settlement.md diff --git a/backend/src/config/alertRegistry.ts b/backend/src/config/alertRegistry.ts index 839e422a..0263bc9e 100644 --- a/backend/src/config/alertRegistry.ts +++ b/backend/src/config/alertRegistry.ts @@ -37,8 +37,9 @@ export const ALERT_REGISTRY: Record = { }, redis_connection_failure: { routing: "page", - runbookUrl: "docs/runbooks/incident-response.md#severity-levels", - description: "Redis is unreachable, degrading caching, sessions, and rate limiting.", + runbookUrl: "docs/runbooks/redis-outage-active-settlement.md", + description: + "Redis is unreachable, failing auth/settlement paths closed and degrading caching, queues, and rate limiting.", }, cache_unavailable: { routing: "ticket", diff --git a/docs/redis-resilience.md b/docs/redis-resilience.md index d4d27855..7ba9d04e 100644 --- a/docs/redis-resilience.md +++ b/docs/redis-resilience.md @@ -16,7 +16,8 @@ Redis backs queues, locks, cache, and rate limiting. This document defines the * | **Cache** (`cacheGet/Set`, `streamCache`, `quoteCache`, `dailyReconciliation`, `feature-flags`) | `GET/SET EX` | **Fail-open graceful** | `cacheGet` → `null` (miss), `cacheSet` → no-op, callers fall back to DB/Horizon. Warn + `cache_unavailable`. | `cache_unavailable` | | **Rate limiting** | `express-rate-limit` MemoryStore (wallet/IP keying) | **Fail-open graceful (documented choice)** | **Allow request** with per-pod counters (lenient). Do NOT deny traffic. Rationale: routine Redis upgrade must not cause global deadlock. Alternatives considered: fail-closed (deny all when Redis down) rejected — unacceptable for trade flows on mobile networks. | `redis_connection_failure` (page, informational) | | **Queues** (BullMQ: `webhooks`, `notifications`, `exports`, `trade-expiry`, `reconciliation` + DLQs) | BullMQ + `createQueueConnection()` per queue/worker | **Degrade-queue, resume on reconnect** | Producers throw transiently, workers pause. Connections use `retryStrategy` exponential backoff (200ms*attempt capped 5s, 20 retries). Jobs are durable in Redis (AOF + RDB) and remain until processed. DLQs capture failed jobs after 3 attempts. On reconnect, workers re-attach and drain. | `redis_connection_failure` + Grafana `redis_key_length_total{key=~"bull:.*:wait"}` | -| **Auth challenge / revocation** | `challenge:{addr}`, `revoked_jti:{jti}`, `auth:challenge-failures:{id}` | **Fail-closed (503)** | Challenge generation/verification and `isTokenRevoked` deny auth when Redis unavailable. Prevents use of revoked tokens during split-brain. | `redis_connection_failure` | +| **Auth challenge / revocation / token version** | `challenge:{addr}`, `revoked_jti:{jti}`, `token_version:{addr}`, `auth:challenge-failures:{id}` | **Fail-closed (503)** | Challenge generation/verification, `isTokenRevoked`, and `getTokenVersion` deny auth when Redis unavailable. Because every authenticated request re-checks revocation/version, **all authenticated trade and settlement routes can return 503 during a Redis outage**. Prevents use of revoked tokens during split-brain. | `redis_connection_failure` (page) | +| **Admin session registry** | `admin_session:{jti}`, `admin_sessions:{wallet}` | **Fail-closed (401/503)** | Device-bound admin session lookup/registration fails closed; admin actions (including clawback/terminate) are unavailable until Redis recovers. | `redis_connection_failure` (page) | | **Feature flags** | `feature:` | **Fail-open graceful** | Fall back to DB/default rollout. | `cache_unavailable` | ## Fail-closed verification (payout-safety-critical) @@ -54,9 +55,10 @@ Coverage: See `backend/src/__tests__/redis.resilience.test.ts`. -## Maintenance runbook +## Runbooks -See `docs/runbooks/redis-maintenance.md` for the maintenance window procedure that guarantees no payout risk. +- `docs/runbooks/redis-maintenance.md` — planned maintenance/failover with no payout risk. +- `docs/runbooks/redis-outage-active-settlement.md` — unplanned outage during an active trade settlement window, including impact matrix, recovery ordering, and payout-intent reconciliation. ## References @@ -66,3 +68,4 @@ See `docs/runbooks/redis-maintenance.md` for the maintenance window procedure th - `backend/src/jobs/queue.ts`, `deadLetter.ts`, workers - `infra/terraform/modules/redis` (ElastiCache replication_group multi-AZ, auto-failover) - `docs/runbooks/redis-maintenance.md` +- `docs/runbooks/redis-outage-active-settlement.md` diff --git a/docs/runbooks/redis-maintenance.md b/docs/runbooks/redis-maintenance.md index bca512d4..c7798659 100644 --- a/docs/runbooks/redis-maintenance.md +++ b/docs/runbooks/redis-maintenance.md @@ -75,7 +75,7 @@ redis-cli -h ping # PONG | Failure | Mitigation | |---|---| | Split-brain double payout attempted | Blocked by DB `lockedAt` + Redis `clawback:lock:*` fail-closed 503. Even if Redis partitions, DB single source prevents double payout. | -| Global deadlock (all requests 503) | Only payout-critical paths are 503; cache/rate-limit/queues are fail-open/degrade. Trade creation/listing remain available via DB fallback. | +| Global deadlock (all requests 503) | Auth revocation/token-version checks fail closed, so authenticated trade and settlement routes can return 503 while Redis is down. Cache/rate-limit/queues fail open or degrade; unauthenticated/cache-only paths stay available. See `docs/runbooks/redis-outage-active-settlement.md`. | | Queue job loss | BullMQ `attempts:3` + DLQ + `retryStrategy` ensures no loss; verify `reconciliation` daily sweep still scheduled (`0 2 * * *`). | | Auth outage | Logins 503, but existing sessions continue (JWT stateless). No mass logout. | @@ -88,6 +88,7 @@ redis-cli -h ping # PONG ## Links - `docs/redis-resilience.md` +- `docs/runbooks/redis-outage-active-settlement.md` (unplanned outage during settlement) - `backend/src/lib/redisPolicy.ts` - `backend/src/__tests__/redis.resilience.test.ts` - `infra/terraform/modules/redis/main.tf` (multi-AZ, auto_failover) diff --git a/docs/runbooks/redis-outage-active-settlement.md b/docs/runbooks/redis-outage-active-settlement.md new file mode 100644 index 00000000..b0db458c --- /dev/null +++ b/docs/runbooks/redis-outage-active-settlement.md @@ -0,0 +1,238 @@ +# Runbook — Redis Outage During Active Trade Settlement + +> Severity: P1 (fund-safety review required) | Audience: On-call, SRE, Backend, Admin operators +> Trigger alerts: `redis_connection_failure`, `cache_unavailable`, `job_missed_heartbeat` +> Related: `docs/redis-resilience.md`, `docs/runbooks/redis-maintenance.md`, +> `docs/runbooks/incident-response.md`, `docs/runbooks/emergency-clawback.md` + +## Goal + +Restore Redis safely while an escrow/trade settlement window is active, without +double-paying a trade, losing a durable queue job, or replaying a clawback. The +DB (`stream.lockedAt`, `PayoutIntent.idempotencyKey`, trade status) is the +source of truth for payout safety — Redis loss must never be treated as +permission to retry a fund movement. + +## 1. Confirm and scope the outage + +1. Acknowledge `redis_connection_failure` and open an incident channel + (`#incident-YYYY-MM-DD-redis`) plus a tracking issue. +2. Confirm Redis is actually down (not just a single pod's view): + ```bash + redis-cli -h ping # expect PONG + redis-cli -h info replication + ``` +3. Check the backend view: + ```bash + curl -sf https:///health | jq '.checks.redis' + curl -sf https:///health/ready | jq . + ``` + Expected during an outage: `/health` is `degraded` (Redis down), and + `/health/ready` is `not_ready` until Redis responds. `/health/startup` + prevents a pod from booting without Redis. +4. Check infra signals: ElastiCache events/failover status, `CPUUtilization`, + `DatabaseMemoryUsagePercentage`, `Evictions`, `ReplicationLag`, + `CurrConnections`, and K8s `redis-service` pod restarts/OOMKills. +5. Establish blast radius: + - Is this a single node, a primary failover, or total loss? + - Which regions/pods still reach Redis? + - Is a settlement/clawback/reconciliation job running right now? + +## 2. Expected impact while Redis is unavailable + +| Consumer | Behavior on Redis loss | Settlement implication | +|---|---|---| +| Auth challenge / token revocation / token version | Fail-closed: `503` `REDIS_UNAVAILABLE` / revocation check failure | **All authenticated trade and settlement endpoints can return 503/timeout**, including create/deposit/confirm/release/dispute, because token validation checks Redis on every request. Existing JWTs do not bypass this. | +| Admin sessions (`admin_session:*`) | Fail-closed: admin session registry check fails (`401`/`503`); step-up registration fails | Admin actions and clawbacks are unavailable during the outage | +| Clawback distributed lock (`clawback:lock:*`) | Fail-closed `503 REDIS_UNAVAILABLE_FAIL_CLOSED` | **Never retry a clawback until Redis is `ready`.** Prevents split-brain double payout | +| Stream lock | DB-backed (`stream.lockedAt`); Redis does not unlock streams | Locked streams stay locked; do not clear DB locks as an outage workaround | +| Idempotency (`idempotency:*`, `idempotency:lock:*`) | Fail-open: request proceeds without caching, emits `cache_unavailable` | Payout routes still rely on `PayoutIntent.idempotencyKey` DB uniqueness. Non-payout routes lose replay protection for the outage duration | +| Rate limiting | In-memory `MemoryStore`, per-pod; fail-open | Traffic may be more lenient; no settlement block | +| Cache / feature flags | Fail-open; DB/default fallback | Higher DB/Horizon load; flags fall back to defaults | +| Queues (webhooks, notifications, exports, trade-expiry, reconciliation, PII scan) | Producers throw transiently, workers pause, connections retry with exponential backoff; jobs stay durable in Redis | Settlement-driven notifications/webhooks pause; **trade-expiry and reconciliation sweeps do not run** until Redis recovers | +| Event listener / ledger indexing | Not Redis-backed (DB + Soroban RPC) | On-chain events may continue to be indexed, but queue side-effects pause | + +If `/health/ready` is `not_ready` on every pod, the load balancer may return +503 before requests reach the app. Treat that as expected during a total +outage, not as a separate application bug. + +## 3. Containment (first 15 minutes) + +1. **Do not** flush Redis, delete `clawback:lock:*` keys, unlock streams in the + DB, or manually replay payout/clawback requests to "clear" the outage. +2. Pause admin-initiated fund operations (clawback, terminate, release) in the + incident channel until recovery checks in §5 pass. +3. If ElastiCache is healthy enough to fail over, follow + `docs/runbooks/redis-maintenance.md`; otherwise restore the node/snapshot. +4. Ask support/comms to tell users that settlement actions may be temporarily + unavailable. Do not promise a retry time until Redis is `ready`. +5. If a settlement transaction was already signed/submitted before the outage, + record its hash and **do not resubmit it**. Track it in §5. + +### Decision points + +| Situation | Action | +|---|---| +| Single replica/nodes down, primary healthy | Fail over/restart the affected node; keep observing | +| Primary down, automatic failover in progress | Wait for ElastiCache failover; check `ReplicationLag` and endpoint DNS | +| Failover not complete within 10 minutes | Escalate to cloud/infra owner; prepare snapshot restore | +| Data loss suspected (AOF/RDB corruption) | Freeze payouts, restore latest snapshot, run §5 before re-enabling admin actions | +| Active clawback lock held when outage started | Wait for lock TTL (30s) after Redis returns; the DB stream lock remains authoritative | +| `PayoutIntent` in `SUBMITTED` with a `txHash` | Query the chain for that hash before changing status; never infer failure from Redis being down | + +## 4. Recovery order + +1. **Restore Redis first.** Prefer failover/reconnect over data deletion. Confirm: + ```bash + redis-cli -h ping + redis-cli -h info persistence # aof_enabled/rdb_last_bgsave_status + redis-cli -h info replication + ``` +2. Confirm the backend has reconnected and is ready: + ```bash + curl -sf https:///health | jq '.status, .checks.redis' + curl -sf https:///health/ready | jq .status # expect "ready" + ``` + Logs should show `Queue Redis ready — consumers will resume` and Redis + recovery messages. +3. Inspect for stale locks. Idempotency locks expire after 30s; clawback locks + expire after 30s. Let TTLs expire rather than mass-deleting keys: + ```bash + redis-cli --scan --pattern 'clawback:lock:*' + redis-cli --scan --pattern 'idempotency:lock:*' + ``` + Non-zero keys immediately after recovery are expected; re-check after 60s. +4. Verify queues are draining and check DLQ depth before replaying anything: + ```bash + curl -sf https:///api/admin/dlq//depth -H "Authorization: Bearer " + ``` + Queues: `webhooks`, `notifications`, `exports`, `trade-expiry`, + `reconciliation`, `pii-log-scan`. Replay only after confirming the job is + not already applied (idempotency/DB check). +5. Run the settlement integrity checks in §5 **before** re-enabling admin fund + actions. +6. Once checks pass, resume normal operations and announce recovery. + +## 5. Settlement and data-integrity checks + +Run these read-only checks against the primary DB. Investigate every row before +mutating status. + +```sql +-- Unresolved payout intents: the highest-risk retry surface +SELECT "idempotencyKey", kind, "tradeId", "milestoneIndex", "txHash", + status, "duplicateAttempts", "submittedAt", "confirmedAt", "updatedAt" +FROM "PayoutIntent" +WHERE status IN ('PENDING', 'SUBMITTED') +ORDER BY "updatedAt" ASC; + +-- Streams locked across the outage: do not clear these as part of recovery +SELECT "streamId", status, "lockedAt", "lockedBy", "lockReason" +FROM "Stream" +WHERE "lockedAt" IS NOT NULL; + +-- Clawbacks recorded before/during the window +SELECT "streamId", admin, amount, timestamp, "txHash" +FROM "StreamClawbackEvent" +ORDER BY timestamp DESC +LIMIT 50; + +-- Trades that may have advanced but not completed +SELECT "tradeId", status, "fundedAt", "deliveredAt", "completedAt", "updatedAt" +FROM "Trade" +WHERE status NOT IN ('COMPLETED', 'CANCELLED', 'EXPIRED') +ORDER BY "updatedAt" DESC +LIMIT 100; + +-- Event listener lag / recent chain processing +SELECT "ledgerSequence", "contractId", "eventId", "processedAt" +FROM "ProcessedEvent" +ORDER BY "ledgerSequence" DESC +LIMIT 10; +``` + +For every `PayoutIntent` with `status = 'SUBMITTED'` and a `txHash`: + +1. Look up the transaction on Soroban RPC/Horizon. +2. If confirmed, call the normal confirmation path (or the service method) so + the intent becomes `CONFIRMED`; do not create a new intent. +3. If definitively failed/not found, follow the normal failure path and record + the reason. Be conservative: an RPC timeout is not proof of failure. +4. Never resubmit a payout with the same idempotency key through a different + code path. `PayoutIntent.idempotencyKey` is the DB-level replay guard. + +Then run the reconciliation sweep manually (or wait for the next 02:00 UTC +sweep) and confirm no new `reconciliation_drift_warning` / +`reconciliation_drift_critical` alerts. Verify the latest indexed ledger is +advancing and indexer lag is back to normal. + +## 6. Communication + +Post an initial update within 10 minutes: + +> P1: Redis outage affecting authenticated settlement actions. On-chain funds +> are safe. New deposit/release/clawback requests may return 503. We are +> failing over/restoring Redis and will verify payout intents before re-enabling +> admin actions. + +Post recovery only after §5 passes: + +> Recovery: Redis is ready, queues are draining, and payout intents / +> clawbacks have been reconciled against the chain. Settlement actions are +> re-enabled. + +Escalate to the eng lead + treasury/admin key holder immediately if: + +- any `PayoutIntent` is `SUBMITTED` with an unverifiable `txHash`, +- a clawback lock or stream lock cannot be accounted for, +- reconciliation reports new drift, +- Redis data loss forces a snapshot restore. + +## 7. Rollback + +- ElastiCache: automatic failover first; if the new primary is unhealthy, + restore the latest snapshot (`snapshot_retention_limit=5`) and replay the + integrity checks in §5. +- Kubernetes `redis-service`: `kubectl rollout undo deployment/redis` or + re-apply the previous manifest. Do not delete the PVC unless data loss is + confirmed and accepted. +- Backend: no code rollback is expected for a Redis outage. If a deploy caused + the outage, roll back the deploy and still follow §5 before re-enabling + payouts. + +## 8. Closeout + +- [ ] Redis `PING`/persistence/replication verified +- [ ] `/health` and `/health/ready` green; no restart loop +- [ ] Stale `clawback:lock:*` / `idempotency:lock:*` keys gone or explained +- [ ] Queue wait/active counts draining; DLQ depth checked and replay decisions recorded +- [ ] Every unresolved `PayoutIntent` accounted for against the chain +- [ ] No stream locks cleared without rationale +- [ ] Reconciliation sweep clean after recovery +- [ ] Incident timeline + postmortem filed within 2 business days + (`docs/runbooks/postmortem-template.md`) +- [ ] `pnpm --filter backend test -- redis.resilience` passes locally/CI + +## 9. Tabletop checklist + +Run this scenario quarterly or whenever the Redis/auth/queue wiring changes: + +1. Simulate Redis loss in staging while a trade is `FUNDED` and a clawback lock + is held. +2. Confirm authenticated settlement calls fail closed (503) and that no retry + produces a second payout. +3. Confirm the event listener catches up and queue jobs drain after reconnect. +4. Confirm `PayoutIntent` and `StreamClawbackEvent` records reconcile with the + chain. +5. Record gaps in this runbook, not just in the retro. + +## Links + +- `docs/redis-resilience.md` — per-consumer policy table and invariants +- `docs/runbooks/redis-maintenance.md` — planned maintenance/failover procedure +- `docs/runbooks/incident-response.md` — severity, IC role, comms +- `docs/runbooks/emergency-clawback.md` — clawback-specific safety steps +- `docs/idempotency-redis-roundtrips.md` — idempotency lock/caching design +- `backend/src/lib/redisPolicy.ts`, `backend/src/services/streamClawback.service.ts` +- `backend/src/services/payoutIntent.service.ts`, `backend/src/jobs/queue.ts` +- `backend/src/__tests__/redis.resilience.test.ts` From 88c7ae706d487b735d5d9d3f9ed405ff8dea9ced Mon Sep 17 00:00:00 2001 From: xsthar Date: Mon, 19 Oct 2026 15:02:18 +0000 Subject: [PATCH 3/4] docs(security): audit PII encryption coverage and extend verifier (#130) --- backend/scripts/verify-pii-encryption.ts | 3 +- docs/pii-encryption.md | 49 ++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/backend/scripts/verify-pii-encryption.ts b/backend/scripts/verify-pii-encryption.ts index 60985d85..5ca1a354 100644 --- a/backend/scripts/verify-pii-encryption.ts +++ b/backend/scripts/verify-pii-encryption.ts @@ -23,7 +23,7 @@ const prisma = new PrismaClient(); const CIPHERTEXT_PATTERN = /^v\d+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/i; interface PiiColumnSpec { - model: "deliveryManifest"; + model: "deliveryManifest" | "tradeNote"; table: string; idColumn: string; column: string; @@ -35,6 +35,7 @@ const PII_COLUMNS: PiiColumnSpec[] = [ { model: "deliveryManifest", table: "DeliveryManifest", idColumn: "tradeId", column: "driverIdNumber" }, { model: "deliveryManifest", table: "DeliveryManifest", idColumn: "tradeId", column: "vehicleRegistration" }, { model: "deliveryManifest", table: "DeliveryManifest", idColumn: "tradeId", column: "routeDescription" }, + { model: "tradeNote", table: "TradeNote", idColumn: "id", column: "content" }, ]; async function verify(): Promise { diff --git a/docs/pii-encryption.md b/docs/pii-encryption.md index 8f1505c5..b82d87c0 100644 --- a/docs/pii-encryption.md +++ b/docs/pii-encryption.md @@ -8,6 +8,8 @@ ## 1. Classified PII Inventory +**Last audited against `backend/prisma/schema.prisma`: 2026-10-19.** + | Column | Table | Classification | Encrypted? | Search need | |---|---|---|---|---| | `driverName` | `DeliveryManifest` | Direct identifier (name) | ✅ App-layer AES-256-GCM | No — mediator/seller view only | @@ -15,9 +17,46 @@ | `vehicleRegistration` | `DeliveryManifest` | Indirect identifier | ✅ App-layer AES-256-GCM | No | | `routeDescription` | `DeliveryManifest` | Free-text, may contain address fragments | ✅ App-layer AES-256-GCM | No | | `driverNameHash` / `driverIdHash` | `DeliveryManifest` | SHA-256 blind index of the above | N/A (already a one-way hash) | Yes — equality match, see §3 | +| `content` | `TradeNote` | Private free-text note (may contain names/addresses/contact details) | ✅ App-layer AES-256-GCM (`TradeNotesService`) | No — decrypt only for author/admin | +| `displayName` | `User` | Direct identifier (name) | ❌ **Not encrypted — gap F2** | No | +| `phoneNumber` | `NotificationPreference` | Direct identifier (E.164 phone number) | ❌ **Not encrypted — gap F1** | No today; add blind index only if lookup is ever required | +| `reason` | `Dispute` | Free-text, may contain direct identifiers or contact details | ❌ **Not encrypted — gap F3** | No | +| `filename` | `TradeEvidence` | Free-text metadata, may contain names/addresses | ❌ **Not encrypted — gap F3** | No | | `walletAddress` | `User`, `Trade.buyerAddress/sellerAddress`, etc. | Pseudonymous identifier (Stellar public key) | Not encrypted — public by design, required for on-chain joins and is not classified as PII under NDPR guidance for public blockchain addresses | Yes — primary key lookups | -There is currently **no phone/contact-number column** in `backend/prisma/schema.prisma`. If one is added (e.g. a `phone` field for delivery contact), it must be added to this table and to `PII_COLUMNS` in `backend/scripts/verify-pii-encryption.ts` before it ships, following the pattern in §2–§3 below. +### Operational free-text reviewed (not classified, may still capture PII) + +| Column | Table | Why it matters | Status | +|---|---|---|---| +| `title`, `message`, `metadata` | `InAppNotification` | Generated from trade events; may echo names, addresses, or contact details | Review notification builders; mask/minimise — gap F4 | +| `responseBody` | `WebhookDeliveryAttempt` | Third-party response bodies can echo user data | Redact/retain — gap F7 | +| `note` | `AdminActionAudit` | Operator-supplied free text | Redact/retain — gap F7 | +| `terminationReason`, `lockReason` | `Stream` | Operator notes | Redact/retain — gap F7 | +| `reason` | `ContractPauseLog` | Operator notes | Redact/retain — gap F7 | +| `lastError`, `notes` | `JobHeartbeat` | Error strings may contain user data | Redact/retain — gap F7 | +| `extra` | `EscrowAudit` | Event metadata; should stay public-chain-derived | Confirm no off-chain PII is copied into it | + +`NotificationPreference.phoneNumber` was added after the original inventory was +written. It is currently persisted and returned in plaintext by +`backend/src/routes/notifications.preferences.routes.ts`; treat this as the +highest-priority gap below. + +## 1a. Audit findings and follow-ups (2026-10-19) + +| ID | Priority | Finding | Follow-up | +|---|---|---|---| +| F1 | High | `NotificationPreference.phoneNumber` is a direct identifier stored and returned in plaintext. | Encrypt on write/decrypt on read using `EncryptionService` (record id = user wallet address), backfill existing rows, add the column to `PII_COLUMNS`, and add route tests asserting ciphertext at rest. No search path exists today; add a blind index only if lookup is later required. | +| F2 | High | `User.displayName` is a direct identifier stored in plaintext. | Decide product requirement: encrypt with the same envelope scheme, or replace with a pseudonymous display value. If equality search is needed, add a blind index (see §3). | +| F3 | Medium | `Dispute.reason` and `TradeEvidence.filename` are free-text fields that can carry names/addresses/contact details; neither is encrypted. | Add input constraints + PII-safe copy in UI, then encrypt at rest or apply documented retention/access controls. Add both to the inventory/verifier once encrypted. | +| F4 | Medium | `InAppNotification.title`/`message`/`metadata` can duplicate PII from trade/manifest events. | Audit notification builders to use masked fields; encrypt or minimise notification payloads. | +| F5 | Medium | `TradeNote.content` is encrypted but was missing from this inventory and from the verifier. | **Fixed in this change:** added to the inventory and to `PII_COLUMNS`. | +| F6 | Medium | `TradeNotesService.listNotes` decrypts note content without the `logPiiAccess()` audit trail used by manifests. | Add decrypt access logging consistent with §5 (resource `trade_note`, recordId = trade ID, actor = caller). | +| F7 | Low | Operator free-text and webhook response bodies can capture PII. | Apply log redaction/retention rules and document them in the relevant ops docs; do not store request bodies or PII in notes/errors. | +| F8 | Low | The KMS-backed envelope roadmap is still outstanding; `JWT_SECRET` remains the root key. | Track via the roadmap in §2. | + +Follow-ups should be filed as dedicated security issues before implementation; +this table is the source of truth until then. Re-run the audit whenever a Prisma +model gains a name, contact, address, identifier, or free-text field. ## 2. Encryption Design @@ -37,9 +76,9 @@ Direct equality search on `driverName` / `driverIdNumber` is supported without d ## 4. Migration & Rollback -Ciphertext columns (`driverName`, `driverIdNumber`, `vehicleRegistration`, `routeDescription`) are plain `VARCHAR`/`TEXT` columns — encryption is transparent at the application layer (`ManifestService.submitManifest` always encrypts before `create`). No separate backfill migration was required because these columns were introduced encrypted-by-default (migration `20260326000001_add_manifest_evidence` plus `feat(backend): encrypt sensitive trade data at rest`). +Ciphertext columns (`driverName`, `driverIdNumber`, `vehicleRegistration`, `routeDescription`, `TradeNote.content`) are plain `VARCHAR`/`TEXT` columns — encryption is transparent at the application layer (`ManifestService.submitManifest` and `TradeNotesService.addNote` always encrypt before `create`). No separate backfill migration was required because these columns were introduced encrypted-by-default (migration `20260326000001_add_manifest_evidence` plus `feat(backend): encrypt sensitive trade data at rest`). -Run `backend/scripts/verify-pii-encryption.ts` after any migration or backfill touching these tables to assert **zero plaintext remnants**: +Run `backend/scripts/verify-pii-encryption.ts` after any migration or backfill touching these tables to assert **zero plaintext remnants** in every column listed as encrypted in §1: ```bash npx ts-node backend/scripts/verify-pii-encryption.ts @@ -51,11 +90,13 @@ It walks every column in the inventory table above and fails (exit code 1) if an Every decrypt of manifest PII (`ManifestService.getManifestByTradeId`) writes a structured audit event via `logPiiAccess()` (`backend/src/lib/piiAudit.ts`), following the same `audit: true` convention as escrow audit logs (`backend/src/lib/escrowAudit.ts`). Each entry records `resource`, `recordId` (trade ID), `fields` decrypted, `actor` (caller wallet address), and `action`. Ship these logs to the same aggregator as other audit events and alert on unexpected `actor` values relative to trade participants/mediator allowlist. +`TradeNotesService.listNotes` currently decrypts `TradeNote.content` without a `logPiiAccess()` event — tracked as follow-up F6 in §1a. + ## 6. Key Rotation Procedure & Drill 1. Introduce a new `keyVersion` (e.g. `v2`) constant. 2. Deploy `EncryptionService` accepting decrypts of both `v1` and `v2` payloads (already supported — `decrypt()` reads `keyVersion` from the payload itself, so old and new ciphertexts co-exist with no code change needed). -3. Run a batch job calling `rotateCiphertext(ciphertext, tradeId, "v2")` over all `DeliveryManifest` rows, writing the result back. +3. Run a batch job calling `rotateCiphertext(ciphertext, tradeId, "v2")` over all `DeliveryManifest` and `TradeNote` rows, writing the result back. 4. Run `verify-pii-encryption.ts` to confirm every row is re-encrypted and well-formed. 5. Once 100% migrated, retire the old master secret. From 4338e2985ca5fe74fd6c61784e582f7bc557f937 Mon Sep 17 00:00:00 2001 From: xsthar Date: Mon, 19 Oct 2026 15:02:24 +0000 Subject: [PATCH 4/4] test(mobile): add Pact consumer suite for trades API (#127) - Add AmanaMobile-AmanaBackend consumer tests covering create, deposit, confirm, release, dispute, detail, and list interactions on the versioned /api/v1 trade routes. - Wire bearer-token injection into the mobile API client and sync it from authStore so authenticated requests match the backend contract. - Send the required dispute category (default OTHER) from the mobile API. - Extend backend Pact provider verification to load the mobile pact file and mount the versioned trade router; stub the ESM-only https-proxy-agent for Jest and stabilise provider state statuses. - Remove a stale React import that broke mobile type-check. --- backend/jest.config.js | 5 + .../src/__tests__/stubs/httpsProxyAgent.ts | 11 + .../src/__tests__/trades.pact.verify.test.ts | 55 +- mobile/package.json | 6 + mobile/pnpm-lock.yaml | 748 ++++++++++++++++-- mobile/src/api/client.ts | 14 +- mobile/src/api/trade.ts | 11 +- mobile/src/screens/AdminStreamScreen.test.tsx | 1 - mobile/src/stores/authStore.ts | 5 + .../pact/pacts/AmanaMobile-AmanaBackend.json | 460 +++++++++++ mobile/tests/pact/trades.pact.test.ts | 307 +++++++ 11 files changed, 1546 insertions(+), 77 deletions(-) create mode 100644 backend/src/__tests__/stubs/httpsProxyAgent.ts create mode 100644 mobile/tests/pact/pacts/AmanaMobile-AmanaBackend.json create mode 100644 mobile/tests/pact/trades.pact.test.ts diff --git a/backend/jest.config.js b/backend/jest.config.js index 713b0850..f893b19e 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -10,6 +10,11 @@ module.exports = { }, moduleNameMapper: { "^@/(.*)$": "/src/$1", + // @pact-foundation/pact@16 requires ESM-only https-proxy-agent@9 at + // module load; Jest's CJS runtime cannot parse it. The agent is never + // constructed unless a proxy env var is set. + "^https-proxy-agent$": + "/src/__tests__/stubs/httpsProxyAgent.ts", }, modulePaths: [""], setupFiles: ["/src/__tests__/setup.ts"], diff --git a/backend/src/__tests__/stubs/httpsProxyAgent.ts b/backend/src/__tests__/stubs/httpsProxyAgent.ts new file mode 100644 index 00000000..e5dd7f50 --- /dev/null +++ b/backend/src/__tests__/stubs/httpsProxyAgent.ts @@ -0,0 +1,11 @@ +/** + * Minimal CommonJS-compatible stand-in for `https-proxy-agent`. + * + * `@pact-foundation/pact@16` requires the ESM-only v9 package at module load, + * which Jest cannot load in its CJS runtime. The agent is only constructed + * when HTTPS_PROXY/HTTP_PROXY is set, which is not the case in CI or local + * provider verification. + */ +export class HttpsProxyAgent { + constructor(_proxy?: string | URL) {} +} diff --git a/backend/src/__tests__/trades.pact.verify.test.ts b/backend/src/__tests__/trades.pact.verify.test.ts index 0c0f2101..43e2e0e0 100644 --- a/backend/src/__tests__/trades.pact.verify.test.ts +++ b/backend/src/__tests__/trades.pact.verify.test.ts @@ -6,6 +6,7 @@ import { Trade, TradeStatus } from '@prisma/client'; import { createTradeRouter } from '../routes/trade.routes'; import { errorHandler } from '../middleware/errorHandler'; import { AuthService } from '../services/auth.service'; +import * as contractServiceModule from '../services/contract.service'; import { ContractService } from '../services/contract.service'; import { TradeService } from '../services/trade.service'; @@ -20,12 +21,18 @@ jest.mock('../services/auth.service', () => ({ })); const JWT_SECRET = 'pact-provider-verify-secret-key-at-least-32-chars'; -const pactFile = path.resolve( - __dirname, - '../../../frontend/tests/pact/pacts/AmanaFrontend-AmanaBackend.json', -); -const pactTest = existsSync(pactFile) ? it : it.skip; -const pactDescribe = existsSync(pactFile) ? describe : describe.skip; +const pactFiles = [ + path.resolve( + __dirname, + '../../../frontend/tests/pact/pacts/AmanaFrontend-AmanaBackend.json', + ), + path.resolve( + __dirname, + '../../../mobile/tests/pact/pacts/AmanaMobile-AmanaBackend.json', + ), +].filter((file) => existsSync(file)); +const pactTest = pactFiles.length > 0 ? it : it.skip; +const pactDescribe = pactFiles.length > 0 ? describe : describe.skip; function createMockTradeService() { return { @@ -71,6 +78,14 @@ function makeTrade(overrides: Partial = {}): Trade { const mockTradeService = createMockTradeService(); const mockContractService = createMockContractService(); +let mockedTradeStatus: TradeStatus = TradeStatus.CREATED; + +jest + .spyOn(contractServiceModule, 'buildConfirmDeliveryTx') + .mockResolvedValue('AAAAAXNvbWUtY29uZmlybS14ZHI='); +jest + .spyOn(contractServiceModule, 'buildReleaseFundsTx') + .mockResolvedValue('AAAAAXNvbWUtcmVsZWFzZS14ZHI='); function createTestApp(): express.Application { const app = express(); @@ -82,6 +97,7 @@ function createTestApp(): express.Application { const tradeRouter = createTradeRouter(); app.use('/trades', tradeRouter); + app.use('/api/v1/trades', tradeRouter); app.use(errorHandler); return app; @@ -95,7 +111,7 @@ pactDescribe('Pact Provider Verification - Trades API', () => { app = createTestApp(); const buyerAddress = 'GDNM7WSJ7VIUVK2TSZ2OQES5XR2663TZEIBFXRDT56B5IRLHERVWSXMU'; - const sellerAddress = 'GA4T33YK6H6D5E7ZQY5W3J2L7F8K9B0N1M2P3Q4R5S6T7U8V9W0X1Y2Z3'; + const sellerAddress = 'GBQ5NYMF2UBXZPH7PCPCUNTONIIIVRRQ24ZDUVAOD4GXLMI5SAQUJDFK'; mockContractService.buildCreateTradeTx.mockResolvedValue({ tradeId: '4294967297', @@ -113,7 +129,7 @@ pactDescribe('Pact Provider Verification - Trades API', () => { buyerAddress, sellerAddress, amountUsdc: '100.00', - status: TradeStatus.CREATED, + status: mockedTradeStatus, }); } return null; @@ -166,23 +182,28 @@ pactDescribe('Pact Provider Verification - Trades API', () => { jest.restoreAllMocks(); }); - pactTest('verifies the provider against the consumer pact', async () => { - const pactDir = path.resolve(__dirname, '../../../frontend/tests/pact/pacts'); + pactTest('verifies the provider against the consumer pacts', async () => { const port = process.env.PACT_PROVIDER_PORT || '3001'; const { Verifier } = await import('@pact-foundation/pact'); const output = await new Verifier({ provider: 'AmanaBackend', providerBaseUrl: `http://localhost:${port}`, - pactUrls: [ - path.resolve(pactDir, 'AmanaFrontend-AmanaBackend.json'), - ], + pactUrls: pactFiles, stateHandlers: { 'a buyer is authenticated': async () => Promise.resolve(), - 'a trade exists in CREATED status': async () => Promise.resolve(), - 'a trade exists in FUNDED status': async () => Promise.resolve(), - 'a trade exists in DELIVERED status': async () => Promise.resolve(), - 'a trade exists with id 4294967297': async () => Promise.resolve(), + 'a trade exists in CREATED status': async () => { + mockedTradeStatus = TradeStatus.CREATED; + }, + 'a trade exists in FUNDED status': async () => { + mockedTradeStatus = TradeStatus.FUNDED; + }, + 'a trade exists in DELIVERED status': async () => { + mockedTradeStatus = TradeStatus.DELIVERED; + }, + 'a trade exists with id 4294967297': async () => { + mockedTradeStatus = TradeStatus.CREATED; + }, 'the user has trades': async () => Promise.resolve(), 'the user has trade statistics': async () => Promise.resolve(), }, diff --git a/mobile/package.json b/mobile/package.json index 33b870de..4801649a 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -48,6 +48,7 @@ }, "devDependencies": { "@eslint/js": "^8.57.1", + "@pact-foundation/pact": "16.5.0", "@expo/config-plugins": "^8.0.8", "@types/jest": "^29.5.12", "@types/node": "^20.10.0", @@ -67,5 +68,10 @@ "react-test-renderer": "^18.3.1", "ts-jest": "29.2.5", "typescript": "^5.6.2" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@pact-foundation/pact-core" + ] } } diff --git a/mobile/pnpm-lock.yaml b/mobile/pnpm-lock.yaml index 96de298e..d1a96bc7 100644 --- a/mobile/pnpm-lock.yaml +++ b/mobile/pnpm-lock.yaml @@ -37,25 +37,25 @@ importers: version: 1.20.0 expo: specifier: ^52.0.0 - version: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + version: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) expo-device: specifier: ^6.0.2 - version: 6.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + version: 6.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) expo-haptics: specifier: ~14.0.1 - version: 14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + version: 14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) expo-local-authentication: specifier: ^14.0.1 - version: 14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + version: 14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) expo-notifications: specifier: ^0.28.0 - version: 0.28.19(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + version: 0.28.19(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) expo-secure-store: specifier: ^13.0.2 - version: 13.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + version: 13.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) expo-sqlite: specifier: 15.0.3 - version: 15.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + version: 15.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -81,6 +81,9 @@ importers: '@expo/config-plugins': specifier: ^8.0.8 version: 8.0.11 + '@pact-foundation/pact': + specifier: 16.5.0 + version: 16.5.0 '@types/jest': specifier: ^29.5.12 version: 29.5.14 @@ -119,7 +122,7 @@ importers: version: 29.7.0(@types/node@20.19.43) jest-expo: specifier: ~52.0.0 - version: 52.0.6(@babel/core@7.29.7)(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(jest@29.7.0(@types/node@20.19.43))(react-dom@19.0.0-rc-6230622a1a-20240610(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)(webpack@5.111.1) + version: 52.0.6(@babel/core@7.29.7)(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(jest@29.7.0(@types/node@20.19.43))(react-dom@19.0.0-rc-6230622a1a-20240610(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)(webpack@5.111.1) prettier: specifier: ^3.3.3 version: 3.9.9 @@ -897,7 +900,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.28': resolution: {integrity: sha512-lvt72KNitGuixYD2l3SZmRKVu2G4zJpmg5V7WfUBNpmUU5oODBw/6qmiJ6kSLAlfDozscUk+BBGknBBzxUrwrA==} @@ -1146,6 +1149,58 @@ packages: resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@pact-foundation/pact-core-darwin-arm64@19.2.0': + resolution: {integrity: sha512-cQvvWfJKS7iMzkunzh/kdgmyVLhAxyr/BQ8fOnMco2M/1+GHR+sOXvhrR1tes+NAYd1xjE3fbg1K2Flno8aDBw==} + cpu: [arm64] + os: [darwin] + + '@pact-foundation/pact-core-darwin-x64@19.2.0': + resolution: {integrity: sha512-UltPXDZ+h1r3JqgIpEBP16bQzB90otd1QbcoeM3XakF9khCrYhW4y9llSuaosyqPFYwCk+mLaZTl/4iitJJaow==} + cpu: [x64] + os: [darwin] + + '@pact-foundation/pact-core-linux-arm64-glibc@19.2.0': + resolution: {integrity: sha512-amiv4COurH1FRa7DSH3ks4UPQCb5J3x4GKrArf8UsTOkNhVGFUWVl83LOELj9Gtk67GwJ96iF7/fQps8I/iFIA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pact-foundation/pact-core-linux-arm64-musl@19.2.0': + resolution: {integrity: sha512-tGWF7dP72Ho1A1PApyUqUMRI/e+gfbPxfaXxwWMXNh8JLK2jQQnP4bWymGwHQ4vxL7wn8ayx8I5y6x1vk/2+rA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pact-foundation/pact-core-linux-x64-glibc@19.2.0': + resolution: {integrity: sha512-/ZNuWKuFJSBRZH09t7FXqKLy5koCaMIZ4RdbockLYlNo8uWF2ALfRCllj+SbXPLK+T+lVnBrs5s94X2cu4Rc7A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pact-foundation/pact-core-linux-x64-musl@19.2.0': + resolution: {integrity: sha512-n0m39CzVkq8J2alir1redm0rAdUVQzkKJqYBQdRmhEIa+QeGjUrJq+gmakUysoJxfSY4UthnJ9jS8SoyiZ+k6A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pact-foundation/pact-core-windows-x64@19.2.0': + resolution: {integrity: sha512-WZSDBL1Sn8y5+YJTK+HDiS/Fn9th1M4QJW2dflQ8UFwoaIh6yb0TrJjtuWbDGIwNeD4Sv8CJ5jJOf1nyNjFLfg==} + cpu: [x64] + os: [win32] + + '@pact-foundation/pact-core@19.2.0': + resolution: {integrity: sha512-7EB2e850hmBzGnwOYTRj4TCFCJQa9zaOy53bK+ybzEDx801olf0gVlYqMYHt1EsHUZzmtS5uDybpr9UMcZQxgg==} + engines: {node: '>=20'} + cpu: [x64, ia32, arm64] + os: [darwin, linux, win32] + + '@pact-foundation/pact@16.5.0': + resolution: {integrity: sha512-9YgmU6fDJGQVYoAm2ez52dyKfQwFzW47GlrT8IAKuzjL6qZVZ9+ael6myeMbQqz7J5lzEGLoUi2PMkBFFgQxRw==} + engines: {node: '>=20'} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1557,6 +1612,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} @@ -1582,6 +1641,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -1719,6 +1782,10 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1835,6 +1902,10 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + bplist-creator@0.0.7: resolution: {integrity: sha512-xp/tcaV3T5PCiaY04mXga7o/TE+t95gqeLmADeBI1CvZtdWTbgBt3uLpvh4UWtenKeBhCV6oVxGk38yZr2uYEA==} @@ -2000,6 +2071,9 @@ packages: charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + check-types@11.2.3: + resolution: {integrity: sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==} + child-process-promise@2.2.1: resolution: {integrity: sha512-Fi4aNdqBsr0mv+jgWxcZ/7rAIC2mgihrptyVI4foh/rrjY/3BNjfP9+oaiFx/fzim+1ZyCNBae0DlyfQhSugog==} @@ -2080,6 +2154,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2124,9 +2201,25 @@ packages: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-js-compat@3.50.0: resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} engines: {node: '>=6.4.0'} @@ -2197,6 +2290,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2292,6 +2388,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -2339,6 +2439,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + drange@1.1.1: + resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==} + engines: {node: '>=4'} + dtrace-provider@0.8.8: resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} engines: {node: '>=0.10'} @@ -2545,6 +2649,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -2673,6 +2780,13 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} + engines: {node: '>= 18'} + + fast-copy@4.2.0: + resolution: {integrity: sha512-WSlnvPXaMdUjFdgxPkovE4e5aslybVod4TNKrEKKyoA8GoHZzbDLlhDXZOvqjtFgjtOHd9TYCstkUcGXy1+SEA==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2686,6 +2800,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.1.8: resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} @@ -2738,6 +2855,10 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-cache-dir@2.1.0: resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} engines: {node: '>=6'} @@ -2804,6 +2925,10 @@ packages: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + freeport-async@2.0.0: resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} engines: {node: '>=8'} @@ -2812,6 +2937,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@11.4.1: resolution: {integrity: sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==} engines: {node: '>=14.14'} @@ -2950,6 +3079,16 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql-tag@2.12.7: + resolution: {integrity: sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -2981,6 +3120,9 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hermes-estree@0.23.1: resolution: {integrity: sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==} @@ -3015,10 +3157,18 @@ packages: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3027,6 +3177,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3214,6 +3368,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -3500,6 +3657,13 @@ packages: join-component@1.1.0: resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-base64@3.9.4: + resolution: {integrity: sha512-PtOMXpEGuP0RRiRXsjzHzl44dMHxSu2CPvAhinupR1tBa88me+1DPqsobl7eupn5UukjLkln1ZnAOAOXOrYG0Q==} + js-message@1.0.7: resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==} engines: {node: '>=0.6.0'} @@ -3748,9 +3912,17 @@ packages: md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-options@3.0.4: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} @@ -3836,6 +4008,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -4008,6 +4184,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -4038,6 +4218,10 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -4111,6 +4295,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -4239,6 +4427,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4262,6 +4453,20 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-pretty@13.2.0: + resolution: {integrity: sha512-jjghD1ML+Di/WxjJDz6j6Y4FT7hIXOlb8h27AyJ+ohtAXJXH4PS/xkv7si+RCCIW59HMgZsMKTDgzdr0RhuRrg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.4.0: + resolution: {integrity: sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -4314,6 +4519,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.1.1: + resolution: {integrity: sha512-oCkNVj4OKnKEcocVBcDfiIugN1/cE6rgfT3nCtzeqZPA8+rIxMaQ0xXzIkGP8Gj2qUaP1UR7uhJ1VD8lUCpzCg==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -4337,6 +4545,19 @@ packages: proper-lockfile@3.2.0: resolution: {integrity: sha512-iMghHHXv2bsxl6NchhEaFck8tvX3F9cknEEh1SUpguUOBjN7PAAW9BLzmbc1g/mCD1gY3EE2EABBHPJfFdHFmA==} + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -4361,6 +4582,10 @@ packages: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true + qs@6.16.1: + resolution: {integrity: sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==} + engines: {node: '>=0.6'} + query-string@7.1.3: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} @@ -4374,6 +4599,16 @@ packages: queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + ramda@0.32.0: + resolution: {integrity: sha512-GQWAHhxhxWBWA8oIBr1XahFVjQ9Fic6MK9ikijfd4TZHfE2+urfk+irVlR5VOn48uwMgM+loRRBJd6Yjsbc0zQ==} + + randexp@0.5.3: + resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} + engines: {node: '>=4'} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -4381,6 +4616,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4466,6 +4705,9 @@ packages: readline@1.3.0: resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==} + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + recast@0.21.5: resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} engines: {node: '>= 4'} @@ -4561,6 +4803,10 @@ packages: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} + ret@0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -4584,6 +4830,10 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4608,6 +4858,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4635,6 +4889,9 @@ packages: resolution: {integrity: sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==} engines: {node: '>= 10.13.0'} + secure-json-parse@4.1.1: + resolution: {integrity: sha512-lnd81MQEZKjhW/Gd2TQqdd4XP6QYNdDW/EP+I7nTrFHP9y2LmaGPIHk9xsnzcOiEr1c39c/e84xFgGviQKBTRw==} + selfsigned@2.4.1: resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} engines: {node: '>=10'} @@ -4661,6 +4918,10 @@ packages: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + serialize-error@2.1.0: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} @@ -4673,6 +4934,10 @@ packages: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} @@ -4767,6 +5032,12 @@ packages: resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + sonic-boom@5.0.2: + resolution: {integrity: sha512-/JEruzo6yhNVhsLblTRchgTQHIt7SsdJP0YyrfWaNKjIOQDfrdZv9gwYraYCAsmKV39/LAdV31FLL5NCDpoL/A==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4793,6 +5064,10 @@ packages: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -4920,6 +5195,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -5015,6 +5294,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} @@ -5122,6 +5405,10 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -5155,6 +5442,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -5507,7 +5797,9 @@ packages: snapshots: - '@0no-co/graphql.web@1.3.4': {} + '@0no-co/graphql.web@1.3.4(graphql@16.14.2)': + optionalDependencies: + graphql: 16.14.2 '@babel/code-frame@7.10.4': dependencies: @@ -6460,9 +6752,9 @@ snapshots: dependencies: uuid: 8.3.2 - '@expo/cli@0.22.28': + '@expo/cli@0.22.28(graphql@16.14.2)': dependencies: - '@0no-co/graphql.web': 1.3.4 + '@0no-co/graphql.web': 1.3.4(graphql@16.14.2) '@babel/runtime': 7.29.7 '@expo/code-signing-certificates': 0.0.6 '@expo/config': 10.0.11 @@ -6481,8 +6773,8 @@ snapshots: '@expo/ws-tunnel': 1.0.6 '@expo/xcpretty': 4.4.5 '@react-native/dev-middleware': 0.76.9 - '@urql/core': 5.2.0 - '@urql/exchange-retry': 1.3.2(@urql/core@5.2.0) + '@urql/core': 5.2.0(graphql@16.14.2) + '@urql/exchange-retry': 1.3.2(@urql/core@5.2.0(graphql@16.14.2)) accepts: 1.3.8 arg: 5.0.2 better-opn: 3.0.2 @@ -7059,6 +7351,68 @@ snapshots: dependencies: semver: 7.8.5 + '@pact-foundation/pact-core-darwin-arm64@19.2.0': + optional: true + + '@pact-foundation/pact-core-darwin-x64@19.2.0': + optional: true + + '@pact-foundation/pact-core-linux-arm64-glibc@19.2.0': + optional: true + + '@pact-foundation/pact-core-linux-arm64-musl@19.2.0': + optional: true + + '@pact-foundation/pact-core-linux-x64-glibc@19.2.0': + optional: true + + '@pact-foundation/pact-core-linux-x64-musl@19.2.0': + optional: true + + '@pact-foundation/pact-core-windows-x64@19.2.0': + optional: true + + '@pact-foundation/pact-core@19.2.0': + dependencies: + check-types: 11.2.3 + detect-libc: 2.1.2 + node-gyp-build: 4.8.4 + pino: 10.4.0 + pino-pretty: 13.2.0 + underscore: 1.13.8 + optionalDependencies: + '@pact-foundation/pact-core-darwin-arm64': 19.2.0 + '@pact-foundation/pact-core-darwin-x64': 19.2.0 + '@pact-foundation/pact-core-linux-arm64-glibc': 19.2.0 + '@pact-foundation/pact-core-linux-arm64-musl': 19.2.0 + '@pact-foundation/pact-core-linux-x64-glibc': 19.2.0 + '@pact-foundation/pact-core-linux-x64-musl': 19.2.0 + '@pact-foundation/pact-core-windows-x64': 19.2.0 + + '@pact-foundation/pact@16.5.0': + dependencies: + '@pact-foundation/pact-core': 19.2.0 + axios: 1.20.0 + body-parser: 2.3.0 + chalk: 4.1.2 + express: 5.3.0 + graphql: 16.14.2 + graphql-tag: 2.12.7(graphql@16.14.2) + http-proxy: 1.18.1 + https-proxy-agent: 9.1.0 + js-base64: 3.9.4 + lodash: 4.18.1 + ramda: 0.32.0 + randexp: 0.5.3 + router: 2.2.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - debug + - kerberos + - supports-color + + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -7606,16 +7960,16 @@ snapshots: '@ungap/structured-clone@1.4.0': {} - '@urql/core@5.2.0': + '@urql/core@5.2.0(graphql@16.14.2)': dependencies: - '@0no-co/graphql.web': 1.3.4 + '@0no-co/graphql.web': 1.3.4(graphql@16.14.2) wonka: 6.3.6 transitivePeerDependencies: - graphql - '@urql/exchange-retry@1.3.2(@urql/core@5.2.0)': + '@urql/exchange-retry@1.3.2(@urql/core@5.2.0(graphql@16.14.2))': dependencies: - '@urql/core': 5.2.0 + '@urql/core': 5.2.0(graphql@16.14.2) wonka: 6.3.6 '@webassemblyjs/ast@1.14.1': @@ -7713,6 +8067,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + acorn-globals@7.0.1: dependencies: acorn: 8.18.0 @@ -7738,6 +8097,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@9.0.0: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -7890,6 +8251,8 @@ snapshots: at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -8049,6 +8412,20 @@ snapshots: bluebird@3.7.2: {} + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.1 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + bplist-creator@0.0.7: dependencies: stream-buffers: 2.2.0 @@ -8234,6 +8611,8 @@ snapshots: charenc@0.0.2: {} + check-types@11.2.3: {} + child-process-promise@2.2.1: dependencies: cross-spawn: 4.0.2 @@ -8318,6 +8697,8 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -8364,8 +8745,16 @@ snapshots: transitivePeerDependencies: - supports-color + content-disposition@2.0.1: {} + + content-type@2.1.0: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + core-js-compat@3.50.0: dependencies: browserslist: 4.29.1 @@ -8459,6 +8848,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dateformat@4.6.3: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -8527,6 +8918,8 @@ snapshots: detect-libc@1.0.3: {} + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} detox-copilot@0.0.0: {} @@ -8604,6 +8997,8 @@ snapshots: dotenv@16.4.7: {} + drange@1.1.1: {} + dtrace-provider@0.8.8: dependencies: nan: 2.29.0 @@ -8904,6 +9299,8 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@4.0.7: {} + events@3.3.0: {} eventsource@2.0.2: {} @@ -8942,15 +9339,15 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - expo-application@5.9.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-application@5.9.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-asset@11.0.5(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): + expo-asset@11.0.5(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): dependencies: '@expo/image-utils': 0.6.5 - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) invariant: 2.2.4 md5-file: 3.2.3 react: 18.3.1 @@ -8958,52 +9355,52 @@ snapshots: transitivePeerDependencies: - supports-color - expo-constants@16.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-constants@16.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: '@expo/config': 9.0.4 '@expo/env': 0.3.0 - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - supports-color - expo-constants@17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)): + expo-constants@17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)): dependencies: '@expo/config': 10.0.11 '@expo/env': 0.4.2 - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) react-native: 0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1) transitivePeerDependencies: - supports-color - expo-device@6.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-device@6.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) ua-parser-js: 0.7.41 - expo-file-system@18.0.12(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)): + expo-file-system@18.0.12(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) react-native: 0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1) web-streams-polyfill: 3.3.3 - expo-font@13.0.4(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1): + expo-font@13.0.4(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) fontfaceobserver: 2.3.0 react: 18.3.1 - expo-haptics@14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-haptics@14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-keep-awake@14.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1): + expo-keep-awake@14.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) react: 18.3.1 - expo-local-authentication@14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-local-authentication@14.0.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) invariant: 2.2.4 expo-modules-autolinking@2.0.8: @@ -9021,46 +9418,46 @@ snapshots: dependencies: invariant: 2.2.4 - expo-notifications@0.28.19(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-notifications@0.28.19(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: '@expo/image-utils': 0.5.1 '@ide/backoff': 1.0.0 abort-controller: 3.0.0 assert: 2.1.0 badgin: 1.2.3 - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-application: 5.9.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) - expo-constants: 16.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo-application: 5.9.1(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) + expo-constants: 16.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)) fs-extra: 9.1.0 transitivePeerDependencies: - encoding - supports-color - expo-secure-store@13.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): + expo-secure-store@13.0.2(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-sqlite@15.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): + expo-sqlite@15.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): dependencies: - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) react: 18.3.1 react-native: 0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1) - expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): + expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.29.7 - '@expo/cli': 0.22.28 + '@expo/cli': 0.22.28(graphql@16.14.2) '@expo/config': 10.0.11 '@expo/config-plugins': 9.0.17 '@expo/fingerprint': 0.11.11 '@expo/metro-config': 0.19.12 '@expo/vector-icons': 14.0.4 babel-preset-expo: 12.0.12(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7)) - expo-asset: 11.0.5(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) - expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) - expo-file-system: 18.0.12(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) - expo-font: 13.0.4(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1) - expo-keep-awake: 14.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1) + expo-asset: 11.0.5(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) + expo-file-system: 18.0.12(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1)) + expo-font: 13.0.4(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1) + expo-keep-awake: 14.0.3(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(react@18.3.1) expo-modules-autolinking: 2.0.8 expo-modules-core: 2.2.3 fbemitter: 3.0.0 @@ -9081,6 +9478,41 @@ snapshots: exponential-backoff@3.1.3: {} + express@5.3.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 2.0.1 + content-type: 2.1.0 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.8 + qs: 6.16.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-copy@4.2.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -9095,6 +9527,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-uri@3.1.8: {} fastq@1.20.3: @@ -9161,6 +9595,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-cache-dir@2.1.0: dependencies: commondir: 1.0.1 @@ -9228,10 +9673,14 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + forwarded@0.2.0: {} + freeport-async@2.0.0: {} fresh@0.5.2: {} + fresh@2.0.0: {} + fs-extra@11.4.1: dependencies: graceful-fs: 4.2.11 @@ -9405,6 +9854,13 @@ snapshots: graphemer@1.4.0: {} + graphql-tag@2.12.7(graphql@16.14.2): + dependencies: + graphql: 16.14.2 + tslib: 2.8.1 + + graphql@16.14.2: {} + has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -9429,6 +9885,8 @@ snapshots: dependencies: function-bind: 1.1.2 + help-me@5.0.0: {} + hermes-estree@0.23.1: {} hermes-estree@0.25.1: {} @@ -9471,6 +9929,14 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.16.0 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -9478,12 +9944,25 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3 + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + human-signals@2.1.0: {} iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -9650,6 +10129,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -9912,14 +10393,14 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@52.0.6(@babel/core@7.29.7)(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(jest@29.7.0(@types/node@20.19.43))(react-dom@19.0.0-rc-6230622a1a-20240610(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)(webpack@5.111.1): + jest-expo@52.0.6(@babel/core@7.29.7)(expo@52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1))(jest@29.7.0(@types/node@20.19.43))(react-dom@19.0.0-rc-6230622a1a-20240610(react@18.3.1))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1)(webpack@5.111.1): dependencies: '@expo/config': 10.0.11 '@expo/json-file': 9.1.5 '@jest/create-cache-key-function': 29.7.0 '@jest/globals': 29.7.0 babel-jest: 29.7.0(@babel/core@7.29.7) - expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) + expo: 52.0.49(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(graphql@16.14.2)(react-native@0.76.2(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(@types/react@18.3.31)(react@18.3.1))(react@18.3.1) fbemitter: 3.0.0 find-up: 5.0.0 jest-environment-jsdom: 29.7.0 @@ -10171,6 +10652,10 @@ snapshots: join-component@1.1.0: {} + joycon@3.1.1: {} + + js-base64@3.9.4: {} + js-message@1.0.7: {} js-queue@2.0.2: @@ -10425,8 +10910,12 @@ snapshots: crypt: 0.0.2 is-buffer: 1.1.6 + media-typer@1.1.1: {} + memoize-one@5.2.1: {} + merge-descriptors@2.0.0: {} + merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 @@ -10620,6 +11109,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mimic-fn@1.2.0: {} @@ -10726,6 +11219,10 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + neo-async@2.6.2: {} nested-error-stacks@2.0.1: {} @@ -10749,6 +11246,8 @@ snapshots: node-forge@1.4.0: {} + node-gyp-build@4.8.4: {} + node-int64@0.4.0: {} node-ipc@9.2.1: @@ -10827,6 +11326,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 + on-exit-leak-free@2.1.2: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -10958,6 +11459,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} picocolors@1.1.1: {} @@ -10970,6 +11473,42 @@ snapshots: pify@4.0.1: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@13.2.0: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.2.0 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.1 + sonic-boom: 5.0.2 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.4.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.1 + quick-format-unescaped: 4.0.4 + real-require: 1.0.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + pirates@4.0.7: {} pkg-dir@3.0.0: @@ -11012,6 +11551,8 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.1.1: {} + progress@2.0.3: {} promise-polyfill@6.1.0: {} @@ -11041,6 +11582,13 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-agent-negotiate@1.1.0: {} + proxy-from-env@2.1.0: {} pseudomap@1.0.2: {} @@ -11060,6 +11608,11 @@ snapshots: qrcode-terminal@0.11.0: {} + qs@6.16.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + query-string@7.1.3: dependencies: decode-uri-component: 0.2.2 @@ -11075,12 +11628,28 @@ snapshots: dependencies: inherits: 2.0.4 + quick-format-unescaped@4.0.4: {} + + ramda@0.32.0: {} + + randexp@0.5.3: + dependencies: + drange: 1.1.1 + ret: 0.2.2 + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 range-parser@1.2.1: {} + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -11221,6 +11790,8 @@ snapshots: readline@1.3.0: {} + real-require@1.0.0: {} + recast@0.21.5: dependencies: ast-types: 0.15.2 @@ -11329,6 +11900,8 @@ snapshots: onetime: 2.0.1 signal-exit: 3.0.7 + ret@0.2.2: {} + retry@0.12.0: {} reusify@1.1.0: {} @@ -11346,6 +11919,16 @@ snapshots: dependencies: glob: 7.2.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -11376,6 +11959,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sanitize-filename@1.6.4: @@ -11405,6 +11990,8 @@ snapshots: ajv-formats: 3.0.1(ajv@8.20.0) ajv-keywords: 5.1.0(ajv@8.20.0) + secure-json-parse@4.1.1: {} + selfsigned@2.4.1: dependencies: '@types/node-forge': 1.3.14 @@ -11436,6 +12023,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + serialize-error@2.1.0: {} serialize-error@8.1.0: @@ -11451,6 +12054,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + server-only@0.0.1: {} set-function-length@1.2.2: @@ -11553,6 +12165,14 @@ snapshots: slugify@1.6.9: {} + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + sonic-boom@5.0.2: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -11573,6 +12193,8 @@ snapshots: split-on-first@1.1.0: {} + split2@4.2.0: {} + sprintf-js@1.0.3: {} ssri@10.0.6: @@ -11720,6 +12342,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + structured-headers@0.4.1: {} sucrase@3.34.0: @@ -11833,6 +12457,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + throat@5.0.0: {} tinyglobby@0.2.17: @@ -11920,6 +12548,12 @@ snapshots: type-fest@0.7.1: {} + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -11965,6 +12599,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + underscore@1.13.8: {} + undici-types@6.21.0: {} undici@6.29.0: {} diff --git a/mobile/src/api/client.ts b/mobile/src/api/client.ts index a9d88f65..69fb4513 100644 --- a/mobile/src/api/client.ts +++ b/mobile/src/api/client.ts @@ -24,9 +24,21 @@ const apiClient = axios.create({ timeout: 10000, }); +let authToken: string | null = null; + +/** + * Set or clear the bearer token attached to subsequent API requests. + * `authStore` keeps this in sync with the token persisted in SecureStore. + */ +export function setAuthToken(token: string | null): void { + authToken = token; +} + // Add token to requests apiClient.interceptors.request.use((config) => { - // Token would be added here from secure store if needed + if (authToken) { + config.headers.set('Authorization', `Bearer ${authToken}`); + } return config; }); diff --git a/mobile/src/api/trade.ts b/mobile/src/api/trade.ts index 807306f5..3625fed6 100644 --- a/mobile/src/api/trade.ts +++ b/mobile/src/api/trade.ts @@ -44,8 +44,15 @@ export const tradeApi = { return response.data; }, - async initiateDispute(tradeId: string, reason: string): Promise { - const response = await apiClient.post(`/trades/${tradeId}/dispute`, { reason }); + async initiateDispute( + tradeId: string, + reason: string, + category = 'OTHER', + ): Promise { + const response = await apiClient.post(`/trades/${tradeId}/dispute`, { + reason, + category, + }); return response.data; }, }; diff --git a/mobile/src/screens/AdminStreamScreen.test.tsx b/mobile/src/screens/AdminStreamScreen.test.tsx index 606b67cc..6e6a8919 100644 --- a/mobile/src/screens/AdminStreamScreen.test.tsx +++ b/mobile/src/screens/AdminStreamScreen.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { render, waitFor, fireEvent } from '@testing-library/react-native'; import AdminStreamScreen from './AdminStreamScreen'; import * as adminApiModule from '../api/admin'; diff --git a/mobile/src/stores/authStore.ts b/mobile/src/stores/authStore.ts index 7c441496..eee28305 100644 --- a/mobile/src/stores/authStore.ts +++ b/mobile/src/stores/authStore.ts @@ -1,6 +1,8 @@ import { create } from 'zustand'; import * as SecureStore from 'expo-secure-store'; +import { setAuthToken } from '../api/client'; + const REMEMBERED_WALLET_KEY = 'amana_remembered_wallet'; export type AuthRole = 'admin' | 'user' | null; @@ -48,6 +50,7 @@ export const useAuthStore = create((set) => ({ setToken: async (token: string) => { await SecureStore.setItemAsync('amana_token', token); + setAuthToken(token); set({ token }); }, @@ -63,6 +66,7 @@ export const useAuthStore = create((set) => ({ getToken: async () => { try { const token = await SecureStore.getItemAsync('amana_token'); + setAuthToken(token); set({ token }); return token; } catch (error) { @@ -85,6 +89,7 @@ export const useAuthStore = create((set) => ({ // ends the session, but the biometric-unlock shortcut for this // device/wallet pair should still work on the next launch. await SecureStore.deleteItemAsync('amana_token'); + setAuthToken(null); set({ token: null, walletAddress: null, role: null }); }, })); diff --git a/mobile/tests/pact/pacts/AmanaMobile-AmanaBackend.json b/mobile/tests/pact/pacts/AmanaMobile-AmanaBackend.json new file mode 100644 index 00000000..632d8bc7 --- /dev/null +++ b/mobile/tests/pact/pacts/AmanaMobile-AmanaBackend.json @@ -0,0 +1,460 @@ +{ + "consumer": { + "name": "AmanaMobile" + }, + "interactions": [ + { + "description": "a mobile request to build a deposit transaction", + "providerStates": [ + { + "name": "a trade exists in CREATED status" + } + ], + "request": { + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature" + }, + "method": "POST", + "path": "/api/v1/trades/4294967297/deposit" + }, + "response": { + "body": { + "unsignedXdr": "AAAAAXNvbWUtZGVwb3NpdC10eC14ZHI=" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.unsignedXdr": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[A-Za-z0-9+/=]+" + } + ] + } + } + }, + "status": 200 + } + }, + { + "description": "a mobile request to confirm delivery", + "providerStates": [ + { + "name": "a trade exists in FUNDED status" + } + ], + "request": { + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature" + }, + "method": "POST", + "path": "/api/v1/trades/4294967297/confirm" + }, + "response": { + "body": { + "unsignedXdr": "AAAAAXNvbWUtY29uZmlybS14ZHI=" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.unsignedXdr": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[A-Za-z0-9+/=]+" + } + ] + } + } + }, + "status": 200 + } + }, + { + "description": "a mobile request to create a trade", + "providerStates": [ + { + "name": "a buyer is authenticated" + } + ], + "request": { + "body": { + "amountUsdc": "100.00", + "buyerLossBps": 5000, + "sellerAddress": "GBQ5NYMF2UBXZPH7PCPCUNTONIIIVRRQ24ZDUVAOD4GXLMI5SAQUJDFK", + "sellerLossBps": 5000 + }, + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature", + "Content-Type": "application/json" + }, + "method": "POST", + "path": "/api/v1/trades" + }, + "response": { + "body": { + "tradeId": "4294967297", + "unsignedXdr": "AAAAAXNvbWUtY3JlYXRlLXRyYWRlLXhkcg==" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.tradeId": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "\\d+" + } + ] + }, + "$.unsignedXdr": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[A-Za-z0-9+/=]+" + } + ] + } + } + }, + "status": 201 + } + }, + { + "description": "a mobile request to get a trade by id", + "providerStates": [ + { + "name": "a trade exists with id 4294967297" + } + ], + "request": { + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature" + }, + "method": "GET", + "path": "/api/v1/trades/4294967297" + }, + "response": { + "body": { + "amountUsdc": "100.00", + "buyerAddress": "GDNM7WSJ7VIUVK2TSZ2OQES5XR2663TZEIBFXRDT56B5IRLHERVWSXMU", + "buyerLossBps": 5000, + "createdAt": "2026-01-01T00:00:00.000Z", + "sellerAddress": "GBQ5NYMF2UBXZPH7PCPCUNTONIIIVRRQ24ZDUVAOD4GXLMI5SAQUJDFK", + "sellerLossBps": 5000, + "status": "CREATED", + "tradeId": "4294967297", + "updatedAt": "2026-01-01T00:00:00.000Z" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.amountUsdc": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.buyerAddress": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.buyerLossBps": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.createdAt": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "match": "datetime" + } + ] + }, + "$.sellerAddress": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.sellerLossBps": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.status": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.updatedAt": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "match": "datetime" + } + ] + } + } + }, + "status": 200 + } + }, + { + "description": "a mobile request to initiate a dispute", + "providerStates": [ + { + "name": "a trade exists in FUNDED status" + } + ], + "request": { + "body": { + "category": "OTHER", + "reason": "Goods not delivered as agreed" + }, + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature", + "Content-Type": "application/json" + }, + "method": "POST", + "path": "/api/v1/trades/4294967297/dispute" + }, + "response": { + "body": { + "unsignedXdr": "AAAAAXNvbWUtZGlzcHV0ZS14ZHI=" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.unsignedXdr": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[A-Za-z0-9+/=]+" + } + ] + } + } + }, + "status": 200 + } + }, + { + "description": "a mobile request to list trades", + "providerStates": [ + { + "name": "the user has trades" + } + ], + "request": { + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature" + }, + "method": "GET", + "path": "/api/v1/trades", + "query": { + "limit": [ + "10" + ], + "page": [ + "1" + ] + } + }, + "response": { + "body": { + "items": [ + { + "amountUsdc": "100.00", + "buyerAddress": "GDNM7WSJ7VIUVK2TSZ2OQES5XR2663TZEIBFXRDT56B5IRLHERVWSXMU", + "buyerLossBps": 5000, + "createdAt": "2026-01-01T00:00:00.000Z", + "sellerAddress": "GBQ5NYMF2UBXZPH7PCPCUNTONIIIVRRQ24ZDUVAOD4GXLMI5SAQUJDFK", + "sellerLossBps": 5000, + "status": "CREATED", + "tradeId": "4294967297", + "updatedAt": "2026-01-01T00:00:00.000Z" + } + ], + "pagination": { + "limit": 10, + "page": 1, + "total": 1, + "totalPages": 1 + } + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.items": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 1 + } + ] + }, + "$.items[*].amountUsdc": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].buyerAddress": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].buyerLossBps": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].createdAt": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "match": "datetime" + } + ] + }, + "$.items[*].sellerAddress": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].sellerLossBps": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].status": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.items[*].updatedAt": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "match": "datetime" + } + ] + } + } + }, + "status": 200 + } + }, + { + "description": "a mobile request to release funds", + "providerStates": [ + { + "name": "a trade exists in DELIVERED status" + } + ], + "request": { + "headers": { + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature" + }, + "method": "POST", + "path": "/api/v1/trades/4294967297/release" + }, + "response": { + "body": { + "unsignedXdr": "AAAAAXNvbWUtcmVsZWFzZS14ZHI=" + }, + "headers": { + "Content-Type": "application/json" + }, + "matchingRules": { + "body": { + "$.unsignedXdr": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "[A-Za-z0-9+/=]+" + } + ] + } + } + }, + "status": 200 + } + } + ], + "metadata": { + "pact-js": { + "version": "16.5.0" + }, + "pactRust": { + "ffi": "0.5.4", + "models": "1.3.11" + }, + "pactSpecification": { + "version": "3.0.0" + } + }, + "provider": { + "name": "AmanaBackend" + } +} \ No newline at end of file diff --git a/mobile/tests/pact/trades.pact.test.ts b/mobile/tests/pact/trades.pact.test.ts new file mode 100644 index 00000000..4eb86a09 --- /dev/null +++ b/mobile/tests/pact/trades.pact.test.ts @@ -0,0 +1,307 @@ +import { PactV3, MatchersV3 } from '@pact-foundation/pact'; + +import apiClient, { setAuthToken } from '../../src/api/client'; +import { tradeApi } from '../../src/api/trade'; + +const { like, eachLike, regex, datetime } = MatchersV3; + +const TRADE_ID = '4294967297'; +const BUYER_ADDRESS = + 'GDNM7WSJ7VIUVK2TSZ2OQES5XR2663TZEIBFXRDT56B5IRLHERVWSXMU'; +const SELLER_ADDRESS = + 'GBQ5NYMF2UBXZPH7PCPCUNTONIIIVRRQ24ZDUVAOD4GXLMI5SAQUJDFK'; + +type TradeListPayload = { + items: Array<{ tradeId: string; status: string }>; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +}; + +describe('Mobile Trades API Pact Consumer Tests', () => { + const provider = new PactV3({ + consumer: 'AmanaMobile', + provider: 'AmanaBackend', + dir: './tests/pact/pacts', + }); + + const mockToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3YWxsZXRBZGRyZXNzIjoiR0ROTTdXU0o3VklVVksyVFNaMk9RRVM1WFIyNjYzVFpFSUJGWFJEVDU2QjVJUkxIRVJWV1NYTVUiLCJzdWIiOiJHRE5NN1dTSjdWSVVWSzJUU1oyT1FFUzVYUjI2NjNUWkVJQkZYUkRUNTZCNUlSTEhFUlZXU1hNVSJ9.signature'; + let originalBaseUrl: string | undefined; + + beforeAll(() => { + originalBaseUrl = apiClient.defaults.baseURL; + setAuthToken(mockToken); + }); + + afterAll(() => { + setAuthToken(null); + apiClient.defaults.baseURL = originalBaseUrl; + }); + + describe('POST /trades — create a trade', () => { + it('returns tradeId and unsignedXdr', async () => { + provider + .given('a buyer is authenticated') + .uponReceiving('a mobile request to create a trade') + .withRequest({ + method: 'POST', + path: '/api/v1/trades', + headers: { + Authorization: `Bearer ${mockToken}`, + 'Content-Type': 'application/json', + }, + body: { + sellerAddress: SELLER_ADDRESS, + amountUsdc: '100.00', + buyerLossBps: 5000, + sellerLossBps: 5000, + }, + }) + .willRespondWith({ + status: 201, + headers: { 'Content-Type': 'application/json' }, + body: { + tradeId: regex('\\d+', TRADE_ID), + unsignedXdr: regex('[A-Za-z0-9+/=]+', 'AAAAAXNvbWUtY3JlYXRlLXRyYWRlLXhkcg=='), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = await tradeApi.createTrade({ + sellerAddress: SELLER_ADDRESS, + amountUsdc: '100.00', + buyerLossBps: 5000, + sellerLossBps: 5000, + }); + + expect(result.tradeId).toBe(TRADE_ID); + expect(result.unsignedXdr).toBeTruthy(); + }); + }); + }); + + describe('POST /trades/:id/deposit — build deposit transaction', () => { + it('returns an unsigned deposit XDR', async () => { + provider + .given('a trade exists in CREATED status') + .uponReceiving('a mobile request to build a deposit transaction') + .withRequest({ + method: 'POST', + path: `/api/v1/trades/${TRADE_ID}/deposit`, + headers: { Authorization: `Bearer ${mockToken}` }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + unsignedXdr: regex('[A-Za-z0-9+/=]+', 'AAAAAXNvbWUtZGVwb3NpdC10eC14ZHI='), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = await tradeApi.deposit(TRADE_ID); + expect(result.unsignedXdr).toBeTruthy(); + }); + }); + }); + + describe('POST /trades/:id/confirm — confirm delivery', () => { + it('returns an unsigned confirm-delivery XDR', async () => { + provider + .given('a trade exists in FUNDED status') + .uponReceiving('a mobile request to confirm delivery') + .withRequest({ + method: 'POST', + path: `/api/v1/trades/${TRADE_ID}/confirm`, + headers: { Authorization: `Bearer ${mockToken}` }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + unsignedXdr: regex('[A-Za-z0-9+/=]+', 'AAAAAXNvbWUtY29uZmlybS14ZHI='), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = (await tradeApi.confirmDelivery(TRADE_ID)) as unknown as { + unsignedXdr: string; + }; + expect(result.unsignedXdr).toBeTruthy(); + }); + }); + }); + + describe('POST /trades/:id/release — release funds', () => { + it('returns an unsigned release XDR', async () => { + provider + .given('a trade exists in DELIVERED status') + .uponReceiving('a mobile request to release funds') + .withRequest({ + method: 'POST', + path: `/api/v1/trades/${TRADE_ID}/release`, + headers: { Authorization: `Bearer ${mockToken}` }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + unsignedXdr: regex('[A-Za-z0-9+/=]+', 'AAAAAXNvbWUtcmVsZWFzZS14ZHI='), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = await tradeApi.releaseFunds(TRADE_ID); + expect(result.unsignedXdr).toBeTruthy(); + }); + }); + }); + + describe('POST /trades/:id/dispute — initiate dispute', () => { + it('returns an unsigned dispute XDR', async () => { + provider + .given('a trade exists in FUNDED status') + .uponReceiving('a mobile request to initiate a dispute') + .withRequest({ + method: 'POST', + path: `/api/v1/trades/${TRADE_ID}/dispute`, + headers: { + Authorization: `Bearer ${mockToken}`, + 'Content-Type': 'application/json', + }, + body: { + reason: 'Goods not delivered as agreed', + category: 'OTHER', + }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + unsignedXdr: regex('[A-Za-z0-9+/=]+', 'AAAAAXNvbWUtZGlzcHV0ZS14ZHI='), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = (await tradeApi.initiateDispute( + TRADE_ID, + 'Goods not delivered as agreed', + )) as unknown as { unsignedXdr: string }; + expect(result.unsignedXdr).toBeTruthy(); + }); + }); + }); + + describe('GET /trades/:id — get a trade', () => { + it('returns trade details', async () => { + provider + .given(`a trade exists with id ${TRADE_ID}`) + .uponReceiving('a mobile request to get a trade by id') + .withRequest({ + method: 'GET', + path: `/api/v1/trades/${TRADE_ID}`, + headers: { Authorization: `Bearer ${mockToken}` }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + tradeId: TRADE_ID, + buyerAddress: like(BUYER_ADDRESS), + sellerAddress: like(SELLER_ADDRESS), + amountUsdc: like('100.00'), + buyerLossBps: like(5000), + sellerLossBps: like(5000), + status: like('CREATED'), + createdAt: datetime( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + '2026-01-01T00:00:00.000Z', + ), + updatedAt: datetime( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + '2026-01-01T00:00:00.000Z', + ), + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = await tradeApi.getTrade(TRADE_ID); + expect(result.tradeId).toBe(TRADE_ID); + expect(result.buyerAddress).toBe(BUYER_ADDRESS); + expect(result.status).toBeTruthy(); + }); + }); + }); + + describe('GET /trades — list trades', () => { + it('returns the backend paginated envelope', async () => { + provider + .given('the user has trades') + .uponReceiving('a mobile request to list trades') + .withRequest({ + method: 'GET', + path: '/api/v1/trades', + query: { page: '1', limit: '10' }, + headers: { Authorization: `Bearer ${mockToken}` }, + }) + .willRespondWith({ + status: 200, + headers: { 'Content-Type': 'application/json' }, + body: { + items: eachLike({ + tradeId: TRADE_ID, + buyerAddress: like(BUYER_ADDRESS), + sellerAddress: like(SELLER_ADDRESS), + amountUsdc: like('100.00'), + buyerLossBps: like(5000), + sellerLossBps: like(5000), + status: like('CREATED'), + createdAt: datetime( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + '2026-01-01T00:00:00.000Z', + ), + updatedAt: datetime( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + '2026-01-01T00:00:00.000Z', + ), + }), + pagination: { + page: 1, + limit: 10, + total: 1, + totalPages: 1, + }, + }, + }); + + await provider.executeTest(async (mockServer) => { + apiClient.defaults.baseURL = mockServer.url; + + const result = (await tradeApi.listTrades({ + page: 1, + limit: 10, + })) as unknown as TradeListPayload; + + expect(result.items).toHaveLength(1); + expect(result.items[0].tradeId).toBe(TRADE_ID); + expect(result.pagination.total).toBe(1); + }); + }); + }); +});