diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..e65ad0a --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,61 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + api-tests: + name: API Tests + runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: pip install -r api/requirements.txt pytest pytest-cov + + - name: Run API tests with coverage + env: + REDIS_HOST: localhost + REDIS_PORT: 6379 + run: | + cd api + python -m pytest test_app.py -v \ + --cov=app \ + --cov-report=term-missing \ + --cov-fail-under=99 + + ui-tests: + name: UI Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: ui/package-lock.json + + - name: Install dependencies + run: cd ui && npm ci + + - name: Run UI tests with coverage + run: | + cd ui + npx vitest run --coverage \ + --coverage.thresholds.lines=99 diff --git a/.gitignore b/.gitignore index b345356..ea85e57 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,10 @@ target/ # Ralph backup directories (created by migration) .ralph_backup_* + +# Coverage reports +ui/coverage/ +api/.coverage + +# TypeScript build info +*.tsbuildinfo diff --git a/README.md b/README.md index e687d96..c72ad7d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@

How It Works · + Deployment · Getting Started · Architecture · Internationalization · @@ -143,6 +144,32 @@ https://example.com/s/Kx7mP2nQ?lng=en#iZcjqbPIBnrWwHHkv_KDWeDcUr9hi3A0oMaVbgCVLr | **Versioned format** | Ciphertext includes version byte for future algorithm upgrades | | **Short aliases** | 8-char base62 IDs (62^8 = 218 trillion), atomic collision-free generation | +## Deployment + +Only Once Share can be used in two ways: + +### Cloud (Hosted) + +Start sharing secrets immediately at **[https://ooshare.io](https://ooshare.io)** — no setup required. The hosted version runs the same open-source code from this repository. + +### On-Premise (Self-Hosted) + +If your organization requires full control over the infrastructure — for compliance, data residency, or security policies — you can deploy Only Once Share on your own servers. + +**What you need:** +- A container runtime (Docker, Kubernetes, ECS, etc.) +- A Redis instance (managed or self-hosted) +- A reverse proxy or load balancer for TLS termination + +**Steps:** +1. Clone this repository +2. Build the API and UI Docker images (see [Getting Started](#getting-started)) +3. Deploy a Redis instance and set the `REDIS_URL` environment variable on the API +4. Set `VITE_API_URL` to your API's URL when building the UI (or update the default in `ui/Dockerfile`) +5. Configure DNS and TLS for your domain + +The architecture is stateless (aside from Redis), so it scales horizontally with no changes. All encryption happens client-side — the server never sees plaintext regardless of where it's deployed. + ## Getting Started ### Prerequisites diff --git a/api/.coverage b/api/.coverage deleted file mode 100644 index ec342e4..0000000 Binary files a/api/.coverage and /dev/null differ diff --git a/api/app.py b/api/app.py index abc734c..1de7df8 100644 --- a/api/app.py +++ b/api/app.py @@ -9,6 +9,7 @@ from flask import Flask, request, jsonify from flask_cors import CORS import redis +from posthog import Posthog logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") log = logging.getLogger(__name__) @@ -16,6 +17,14 @@ app = Flask(__name__) CORS(app) +POSTHOG_API_KEY = os.environ.get("POSTHOG_API_KEY") +if POSTHOG_API_KEY: # pragma: no cover + posthog = Posthog(POSTHOG_API_KEY, host="https://us.i.posthog.com") + log.info("PostHog initialized") +else: + posthog = None + log.info("PostHog disabled (POSTHOG_API_KEY not set)") + MAX_CIPHERTEXT_SIZE = 100 * 1024 # 100KB ALIAS_ALPHABET = string.ascii_letters + string.digits ALIAS_LENGTH = 8 @@ -27,7 +36,7 @@ def generate_alias(): REDIS_URL = os.environ.get("REDIS_URL") -if REDIS_URL: +if REDIS_URL: # pragma: no cover log.info("Connecting to Redis via REDIS_URL") r = redis.from_url(REDIS_URL, decode_responses=True, socket_timeout=5) else: @@ -41,10 +50,10 @@ def generate_alias(): socket_timeout=5, ) -try: +try: # pragma: no cover r.ping() log.info("Redis connection established") -except redis.ConnectionError as e: +except redis.ConnectionError as e: # pragma: no cover log.error("Redis connection failed: %s", e) @@ -121,6 +130,9 @@ def create_secret(): if alias is None: log.error("Failed to generate alias after 5 attempts for secret %s", secret_id) + if posthog: + posthog.capture("server", "secret_created", {"ttl_hours": ttl_hours, "has_alias": alias is not None}) + return jsonify({"id": secret_id, "alias": alias}), 201 @@ -160,6 +172,9 @@ def get_secret(secret_id): r.delete(f"alias:{alias_used}") log.info("Alias cleaned up: %s", alias_used) + if posthog: + posthog.capture("server", "secret_retrieved", {"via": "alias" if alias_used else "uuid"}) + return jsonify({"ciphertext": ciphertext, "id": actual_id}) diff --git a/api/requirements.txt b/api/requirements.txt index 205c34e..0d4753a 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -2,3 +2,4 @@ flask==3.1.0 flask-cors==5.0.1 redis==5.2.1 gunicorn==23.0.0 +posthog==3.24.0 diff --git a/api/test_app.py b/api/test_app.py index a5c6bf9..35c1318 100644 --- a/api/test_app.py +++ b/api/test_app.py @@ -6,6 +6,14 @@ from app import app, generate_alias, ALIAS_LENGTH +@pytest.fixture(autouse=True) +def mock_posthog(): + """Mock PostHog so tests don't send real events.""" + mock = MagicMock() + with patch("app.posthog", mock): + yield mock + + @pytest.fixture def client(): app.config["TESTING"] = True @@ -306,6 +314,48 @@ def test_get_secret_alias_points_to_expired_secret(client, mock_redis): assert res.status_code == 404 +# --------------- PostHog events --------------- + + +def test_posthog_secret_created(client, mock_posthog): + client.post("/api/secrets", json={"ciphertext": "dGVzdA==", "ttl_hours": 4}) + mock_posthog.capture.assert_called_with( + "server", "secret_created", {"ttl_hours": 4, "has_alias": True} + ) + + +def test_posthog_secret_retrieved(client, mock_posthog): + res = client.post("/api/secrets", json={"ciphertext": "dGVzdA=="}) + alias = res.get_json()["alias"] + mock_posthog.reset_mock() + + client.get(f"/api/secrets/{alias}") + mock_posthog.capture.assert_called_with( + "server", "secret_retrieved", {"via": "alias"} + ) + + +def test_posthog_secret_retrieved_by_uuid(client, mock_posthog): + res = client.post("/api/secrets", json={"ciphertext": "dGVzdA=="}) + secret_id = res.get_json()["id"] + mock_posthog.reset_mock() + + client.get(f"/api/secrets/{secret_id}") + mock_posthog.capture.assert_called_with( + "server", "secret_retrieved", {"via": "uuid"} + ) + + +def test_posthog_disabled(client, mock_redis): + """When posthog is None, no errors occur.""" + with patch("app.posthog", None): + res = client.post("/api/secrets", json={"ciphertext": "dGVzdA=="}) + assert res.status_code == 201 + secret_id = res.get_json()["id"] + res = client.get(f"/api/secrets/{secret_id}") + assert res.status_code == 200 + + # --------------- main guard --------------- diff --git a/ui/Dockerfile b/ui/Dockerfile index be9b72d..e44d6c4 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -5,7 +5,9 @@ COPY package.json package-lock.json* ./ RUN npm install COPY . . ARG VITE_API_URL=https://oos-api.onrender.com +ARG VITE_POSTHOG_KEY ENV VITE_API_URL=$VITE_API_URL +ENV VITE_POSTHOG_KEY=$VITE_POSTHOG_KEY RUN npm run build # Production stage diff --git a/ui/coverage/base.css b/ui/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/ui/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/ui/coverage/block-navigation.js b/ui/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/ui/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/ui/coverage/clover.xml b/ui/coverage/clover.xml deleted file mode 100644 index 368dea9..0000000 --- a/ui/coverage/clover.xml +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ui/coverage/components/LanguageSelector.tsx.html b/ui/coverage/components/LanguageSelector.tsx.html deleted file mode 100644 index f6c4f47..0000000 --- a/ui/coverage/components/LanguageSelector.tsx.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - Code coverage report for components/LanguageSelector.tsx - - - - - - - - - -

-
-

All files / components LanguageSelector.tsx

-
- -
- 100% - Statements - 17/17 -
- - -
- 90% - Branches - 9/10 -
- - -
- 100% - Functions - 9/9 -
- - -
- 100% - Lines - 15/15 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57  -  -  -  -  -18x -18x -18x -  -32x -  -18x -  -8x -1x -  -  -10x -10x -  -  -  -1x -1x -  -  -18x -  -  -  -6x -  -  -  -  -  -  -  -  -  -30x -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  - 
import { useState, useRef, useEffect } from "react";
-import { useTranslation } from "react-i18next";
-import { LANGUAGES } from "../i18n";
- 
-export default function LanguageSelector() {
-  const { i18n } = useTranslation();
-  const [open, setOpen] = useState(false);
-  const ref = useRef<HTMLDivElement>(null);
- 
-  const current = LANGUAGES.find((l) => l.code === i18n.language) || LANGUAGES[0];
- 
-  useEffect(() => {
-    function handleClick(e: MouseEvent) {
-      if (ref.current && !ref.current.contains(e.target as Node)) {
-        setOpen(false);
-      }
-    }
-    document.addEventListener("mousedown", handleClick);
-    return () => document.removeEventListener("mousedown", handleClick);
-  }, []);
- 
-  function select(code: string) {
-    i18n.changeLanguage(code);
-    setOpen(false);
-  }
- 
-  return (
-    <div className="lang-selector" ref={ref}>
-      <button
-        className="lang-trigger"
-        onClick={() => setOpen(!open)}
-        aria-label="Select language"
-        aria-expanded={open}
-      >
-        <span className="lang-flag">{current.flag}</span>
-      </button>
- 
-      {open && (
-        <div className="lang-dropdown" role="listbox" aria-label="Languages">
-          {LANGUAGES.map((lang) => (
-            <button
-              key={lang.code}
-              className={`lang-option ${lang.code === i18n.language ? "lang-option--active" : ""}`}
-              role="option"
-              aria-selected={lang.code === i18n.language}
-              onClick={() => select(lang.code)}
-            >
-              <span className="lang-flag">{lang.flag}</span>
-              <span>{lang.label}</span>
-            </button>
-          ))}
-        </div>
-      )}
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/components/Layout.tsx.html b/ui/coverage/components/Layout.tsx.html deleted file mode 100644 index ba5e99c..0000000 --- a/ui/coverage/components/Layout.tsx.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - Code coverage report for components/Layout.tsx - - - - - - - - - -
-
-

All files / components Layout.tsx

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45  -  -  -  -  -  -4x -  -4x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useTranslation } from "react-i18next";
-import { Shield, Lock, Eye, Trash2 } from "lucide-react";
-import SecurityModal from "./SecurityModal";
-import LanguageSelector from "./LanguageSelector";
- 
-export default function Layout({ children }: { children: React.ReactNode }) {
-  const { t } = useTranslation();
- 
-  return (
-    <div className="layout">
-      <header className="layout-header">
-        <a href="/">
-          <Shield size={22} className="header-icon" />
-          <span className="header-title">{t("header.title")}</span>
-        </a>
-        <div className="header-actions">
-          <LanguageSelector />
-          <SecurityModal />
-        </div>
-      </header>
- 
-      <main className="layout-main">
-        <div className="layout-content">{children}</div>
-      </main>
- 
-      <footer className="layout-footer">
-        <div className="footer-badges">
-          <span className="footer-badge">
-            <Lock size={13} />
-            {t("footer.encryption")}
-          </span>
-          <span className="footer-badge">
-            <Eye size={13} />
-            {t("footer.zeroKnowledge")}
-          </span>
-          <span className="footer-badge">
-            <Trash2 size={13} />
-            {t("footer.autoDelete")}
-          </span>
-        </div>
-      </footer>
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/components/SecurityModal.tsx.html b/ui/coverage/components/SecurityModal.tsx.html deleted file mode 100644 index 783769d..0000000 --- a/ui/coverage/components/SecurityModal.tsx.html +++ /dev/null @@ -1,412 +0,0 @@ - - - - - - Code coverage report for components/SecurityModal.tsx - - - - - - - - - -
-
-

All files / components SecurityModal.tsx

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -17x -17x -  -17x -  -  -  -5x -  -  -  -  -  -  -1x -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useState } from "react";
-import { useTranslation } from "react-i18next";
-import {
-  HelpCircle,
-  X,
-  Shield,
-  Lock,
-  Eye,
-  Trash2,
-  Server,
-  Hash,
-  KeyRound,
-  ShieldCheck,
-} from "lucide-react";
- 
-export default function SecurityModal() {
-  const { t } = useTranslation();
-  const [open, setOpen] = useState(false);
- 
-  return (
-    <>
-      <button
-        className="info-trigger"
-        onClick={() => setOpen(true)}
-        aria-label={t("security.title")}
-      >
-        <HelpCircle size={18} />
-      </button>
- 
-      {open && (
-        <div className="modal-overlay" onClick={() => setOpen(false)}>
-          <div
-            className="modal"
-            role="dialog"
-            aria-modal="true"
-            aria-label={t("security.title")}
-            onClick={(e) => e.stopPropagation()}
-          >
-            <div className="modal-header">
-              <div className="modal-header-left">
-                <Shield size={18} />
-                <h2>{t("security.title")}</h2>
-              </div>
-              <button
-                className="modal-close"
-                onClick={() => setOpen(false)}
-                aria-label="Close"
-              >
-                <X size={18} />
-              </button>
-            </div>
- 
-            <div className="modal-body">
-              <div className="security-item">
-                <div className="security-item-icon"><Lock size={16} /></div>
-                <div>
-                  <h3>{t("security.e2eTitle")}</h3>
-                  <p>{t("security.e2eDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><KeyRound size={16} /></div>
-                <div>
-                  <h3>{t("security.hkdfTitle")}</h3>
-                  <p>{t("security.hkdfDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><ShieldCheck size={16} /></div>
-                <div>
-                  <h3>{t("security.aadTitle")}</h3>
-                  <p>{t("security.aadDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><Eye size={16} /></div>
-                <div>
-                  <h3>{t("security.zkTitle")}</h3>
-                  <p>{t("security.zkDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><Hash size={16} /></div>
-                <div>
-                  <h3>{t("security.keyTitle")}</h3>
-                  <p>{t("security.keyDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><Trash2 size={16} /></div>
-                <div>
-                  <h3>{t("security.oneTimeTitle")}</h3>
-                  <p>{t("security.oneTimeDesc")}</p>
-                </div>
-              </div>
-              <div className="security-item">
-                <div className="security-item-icon"><Server size={16} /></div>
-                <div>
-                  <h3>{t("security.expiryTitle")}</h3>
-                  <p>{t("security.expiryDesc")}</p>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      )}
-    </>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/components/index.html b/ui/coverage/components/index.html deleted file mode 100644 index 2619e55..0000000 --- a/ui/coverage/components/index.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - Code coverage report for components - - - - - - - - - -
-
-

All files components

-
- -
- 100% - Statements - 26/26 -
- - -
- 91.66% - Branches - 11/12 -
- - -
- 100% - Functions - 15/15 -
- - -
- 100% - Lines - 24/24 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
LanguageSelector.tsx -
-
100%17/1790%9/10100%9/9100%15/15
Layout.tsx -
-
100%2/2100%0/0100%1/1100%2/2
SecurityModal.tsx -
-
100%7/7100%2/2100%5/5100%7/7
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/coverage-final.json b/ui/coverage/coverage-final.json deleted file mode 100644 index 909295c..0000000 --- a/ui/coverage/coverage-final.json +++ /dev/null @@ -1,9 +0,0 @@ -{"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/LanguageSelector.tsx": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/LanguageSelector.tsx","statementMap":{"0":{"start":{"line":6,"column":15},"end":{"line":6,"column":null}},"1":{"start":{"line":7,"column":22},"end":{"line":7,"column":null}},"2":{"start":{"line":8,"column":8},"end":{"line":8,"column":null}},"3":{"start":{"line":10,"column":18},"end":{"line":10,"column":null}},"4":{"start":{"line":10,"column":40},"end":{"line":10,"column":64}},"5":{"start":{"line":12,"column":2},"end":{"line":20,"column":null}},"6":{"start":{"line":14,"column":6},"end":{"line":16,"column":null}},"7":{"start":{"line":15,"column":8},"end":{"line":15,"column":null}},"8":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"9":{"start":{"line":19,"column":4},"end":{"line":19,"column":null}},"10":{"start":{"line":19,"column":17},"end":{"line":19,"column":null}},"11":{"start":{"line":23,"column":4},"end":{"line":23,"column":null}},"12":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"13":{"start":{"line":27,"column":2},"end":{"line":54,"column":null}},"14":{"start":{"line":31,"column":23},"end":{"line":31,"column":null}},"15":{"start":{"line":41,"column":12},"end":{"line":50,"column":null}},"16":{"start":{"line":46,"column":29},"end":{"line":46,"column":null}}},"fnMap":{"0":{"name":"LanguageSelector","decl":{"start":{"line":5,"column":24},"end":{"line":5,"column":43}},"loc":{"start":{"line":5,"column":43},"end":{"line":56,"column":null}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":33},"end":{"line":10,"column":34}},"loc":{"start":{"line":10,"column":40},"end":{"line":10,"column":64}},"line":10},"2":{"name":"(anonymous_2)","decl":{"start":{"line":12,"column":12},"end":{"line":12,"column":18}},"loc":{"start":{"line":12,"column":18},"end":{"line":20,"column":5}},"line":12},"3":{"name":"handleClick","decl":{"start":{"line":13,"column":13},"end":{"line":13,"column":25}},"loc":{"start":{"line":13,"column":40},"end":{"line":17,"column":null}},"line":13},"4":{"name":"(anonymous_4)","decl":{"start":{"line":19,"column":11},"end":{"line":19,"column":17}},"loc":{"start":{"line":19,"column":17},"end":{"line":19,"column":null}},"line":19},"5":{"name":"select","decl":{"start":{"line":22,"column":11},"end":{"line":22,"column":18}},"loc":{"start":{"line":22,"column":32},"end":{"line":25,"column":null}},"line":22},"6":{"name":"(anonymous_6)","decl":{"start":{"line":31,"column":17},"end":{"line":31,"column":23}},"loc":{"start":{"line":31,"column":23},"end":{"line":31,"column":null}},"line":31},"7":{"name":"(anonymous_7)","decl":{"start":{"line":40,"column":25},"end":{"line":40,"column":26}},"loc":{"start":{"line":41,"column":12},"end":{"line":50,"column":null}},"line":41},"8":{"name":"(anonymous_8)","decl":{"start":{"line":46,"column":23},"end":{"line":46,"column":29}},"loc":{"start":{"line":46,"column":29},"end":{"line":46,"column":null}},"line":46}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":18},"end":{"line":10,"column":null}},"type":"binary-expr","locations":[{"start":{"line":10,"column":18},"end":{"line":10,"column":69}},{"start":{"line":10,"column":69},"end":{"line":10,"column":null}}],"line":10},"1":{"loc":{"start":{"line":14,"column":6},"end":{"line":16,"column":null}},"type":"if","locations":[{"start":{"line":14,"column":6},"end":{"line":16,"column":null}},{"start":{},"end":{}}],"line":14},"2":{"loc":{"start":{"line":14,"column":10},"end":{"line":14,"column":66}},"type":"binary-expr","locations":[{"start":{"line":14,"column":10},"end":{"line":14,"column":25}},{"start":{"line":14,"column":25},"end":{"line":14,"column":66}}],"line":14},"3":{"loc":{"start":{"line":38,"column":7},"end":{"line":52,"column":null}},"type":"binary-expr","locations":[{"start":{"line":38,"column":7},"end":{"line":38,"column":null}},{"start":{"line":39,"column":8},"end":{"line":52,"column":null}}],"line":38},"4":{"loc":{"start":{"line":43,"column":40},"end":{"line":43,"column":96}},"type":"cond-expr","locations":[{"start":{"line":43,"column":70},"end":{"line":43,"column":94}},{"start":{"line":43,"column":94},"end":{"line":43,"column":96}}],"line":43}},"s":{"0":18,"1":18,"2":18,"3":18,"4":32,"5":18,"6":8,"7":1,"8":10,"9":10,"10":10,"11":1,"12":1,"13":18,"14":6,"15":30,"16":1},"f":{"0":18,"1":32,"2":10,"3":8,"4":10,"5":1,"6":6,"7":30,"8":1},"b":{"0":[18,0],"1":[1,7],"2":[8,8],"3":[18,5],"4":[5,25]},"meta":{"lastBranch":5,"lastFunction":9,"lastStatement":17,"seen":{"f:5:24:5:43":0,"s:6:15:6:Infinity":0,"s:7:22:7:Infinity":1,"s:8:8:8:Infinity":2,"s:10:18:10:Infinity":3,"b:10:18:10:69:10:69:10:Infinity":0,"f:10:33:10:34":1,"s:10:40:10:64":4,"s:12:2:20:Infinity":5,"f:12:12:12:18":2,"f:13:13:13:25":3,"b:14:6:16:Infinity:undefined:undefined:undefined:undefined":1,"s:14:6:16:Infinity":6,"b:14:10:14:25:14:25:14:66":2,"s:15:8:15:Infinity":7,"s:18:4:18:Infinity":8,"s:19:4:19:Infinity":9,"f:19:11:19:17":4,"s:19:17:19:Infinity":10,"f:22:11:22:18":5,"s:23:4:23:Infinity":11,"s:24:4:24:Infinity":12,"s:27:2:54:Infinity":13,"f:31:17:31:23":6,"s:31:23:31:Infinity":14,"b:38:7:38:Infinity:39:8:52:Infinity":3,"f:40:25:40:26":7,"s:41:12:50:Infinity":15,"b:43:70:43:94:43:94:43:96":4,"f:46:23:46:29":8,"s:46:29:46:Infinity":16}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/Layout.tsx": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/Layout.tsx","statementMap":{"0":{"start":{"line":7,"column":12},"end":{"line":7,"column":null}},"1":{"start":{"line":9,"column":2},"end":{"line":42,"column":null}}},"fnMap":{"0":{"name":"Layout","decl":{"start":{"line":6,"column":24},"end":{"line":6,"column":31}},"loc":{"start":{"line":6,"column":76},"end":{"line":44,"column":null}},"line":6}},"branchMap":{},"s":{"0":4,"1":4},"f":{"0":4},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":2,"seen":{"f:6:24:6:31":0,"s:7:12:7:Infinity":0,"s:9:2:42:Infinity":1}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/SecurityModal.tsx": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/components/SecurityModal.tsx","statementMap":{"0":{"start":{"line":17,"column":12},"end":{"line":17,"column":null}},"1":{"start":{"line":18,"column":22},"end":{"line":18,"column":null}},"2":{"start":{"line":20,"column":2},"end":{"line":107,"column":null}},"3":{"start":{"line":24,"column":23},"end":{"line":24,"column":null}},"4":{"start":{"line":31,"column":54},"end":{"line":31,"column":null}},"5":{"start":{"line":37,"column":28},"end":{"line":37,"column":null}},"6":{"start":{"line":46,"column":31},"end":{"line":46,"column":null}}},"fnMap":{"0":{"name":"SecurityModal","decl":{"start":{"line":16,"column":24},"end":{"line":16,"column":40}},"loc":{"start":{"line":16,"column":40},"end":{"line":109,"column":null}},"line":16},"1":{"name":"(anonymous_1)","decl":{"start":{"line":24,"column":17},"end":{"line":24,"column":23}},"loc":{"start":{"line":24,"column":23},"end":{"line":24,"column":null}},"line":24},"2":{"name":"(anonymous_2)","decl":{"start":{"line":31,"column":48},"end":{"line":31,"column":54}},"loc":{"start":{"line":31,"column":54},"end":{"line":31,"column":null}},"line":31},"3":{"name":"(anonymous_3)","decl":{"start":{"line":37,"column":21},"end":{"line":37,"column":22}},"loc":{"start":{"line":37,"column":28},"end":{"line":37,"column":null}},"line":37},"4":{"name":"(anonymous_4)","decl":{"start":{"line":46,"column":25},"end":{"line":46,"column":31}},"loc":{"start":{"line":46,"column":31},"end":{"line":46,"column":null}},"line":46}},"branchMap":{"0":{"loc":{"start":{"line":30,"column":7},"end":{"line":105,"column":null}},"type":"binary-expr","locations":[{"start":{"line":30,"column":7},"end":{"line":30,"column":null}},{"start":{"line":31,"column":8},"end":{"line":105,"column":null}}],"line":30}},"s":{"0":17,"1":17,"2":17,"3":5,"4":1,"5":2,"6":1},"f":{"0":17,"1":5,"2":1,"3":2,"4":1},"b":{"0":[17,5]},"meta":{"lastBranch":1,"lastFunction":5,"lastStatement":7,"seen":{"f:16:24:16:40":0,"s:17:12:17:Infinity":0,"s:18:22:18:Infinity":1,"s:20:2:107:Infinity":2,"f:24:17:24:23":1,"s:24:23:24:Infinity":3,"b:30:7:30:Infinity:31:8:105:Infinity":0,"f:31:48:31:54":2,"s:31:54:31:Infinity":4,"f:37:21:37:22":3,"s:37:28:37:Infinity":5,"f:46:25:46:31":4,"s:46:31:46:Infinity":6}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/i18n/index.ts": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/i18n/index.ts","statementMap":{"0":{"start":{"line":12,"column":0},"end":{"line":34,"column":null}},"1":{"start":{"line":32,"column":48},"end":{"line":32,"column":null}},"2":{"start":{"line":38,"column":25},"end":{"line":45,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":32,"column":31},"end":{"line":32,"column":32}},"loc":{"start":{"line":32,"column":48},"end":{"line":32,"column":null}},"line":32}},"branchMap":{},"s":{"0":6,"1":21,"2":6},"f":{"0":21},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":3,"seen":{"s:12:0:34:Infinity":0,"f:32:31:32:32":0,"s:32:48:32:Infinity":1,"s:38:25:45:Infinity":2}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/lib/api.ts": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/lib/api.ts","statementMap":{"0":{"start":{"line":11,"column":14},"end":{"line":15,"column":null}},"1":{"start":{"line":17,"column":2},"end":{"line":20,"column":null}},"2":{"start":{"line":18,"column":16},"end":{"line":18,"column":null}},"3":{"start":{"line":19,"column":4},"end":{"line":19,"column":null}},"4":{"start":{"line":22,"column":15},"end":{"line":22,"column":null}},"5":{"start":{"line":23,"column":2},"end":{"line":23,"column":null}},"6":{"start":{"line":32,"column":14},"end":{"line":32,"column":null}},"7":{"start":{"line":34,"column":2},"end":{"line":36,"column":null}},"8":{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},"9":{"start":{"line":38,"column":2},"end":{"line":41,"column":null}},"10":{"start":{"line":39,"column":16},"end":{"line":39,"column":null}},"11":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"12":{"start":{"line":43,"column":15},"end":{"line":43,"column":null}},"13":{"start":{"line":44,"column":2},"end":{"line":44,"column":null}}},"fnMap":{"0":{"name":"createSecret","decl":{"start":{"line":6,"column":22},"end":{"line":6,"column":null}},"loc":{"start":{"line":10,"column":31},"end":{"line":24,"column":null}},"line":10},"1":{"name":"getSecret","decl":{"start":{"line":31,"column":22},"end":{"line":31,"column":32}},"loc":{"start":{"line":31,"column":70},"end":{"line":45,"column":null}},"line":31}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":2},"end":{"line":20,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":2},"end":{"line":20,"column":null}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":19,"column":20},"end":{"line":19,"column":58}},"type":"binary-expr","locations":[{"start":{"line":19,"column":20},"end":{"line":19,"column":33}},{"start":{"line":19,"column":33},"end":{"line":19,"column":58}}],"line":19},"2":{"loc":{"start":{"line":23,"column":31},"end":{"line":23,"column":50}},"type":"binary-expr","locations":[{"start":{"line":23,"column":31},"end":{"line":23,"column":45}},{"start":{"line":23,"column":45},"end":{"line":23,"column":50}}],"line":23},"3":{"loc":{"start":{"line":34,"column":2},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":2},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":34},"4":{"loc":{"start":{"line":38,"column":2},"end":{"line":41,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":2},"end":{"line":41,"column":null}},{"start":{},"end":{}}],"line":38},"5":{"loc":{"start":{"line":40,"column":20},"end":{"line":40,"column":60}},"type":"binary-expr","locations":[{"start":{"line":40,"column":20},"end":{"line":40,"column":33}},{"start":{"line":40,"column":33},"end":{"line":40,"column":60}}],"line":40}},"s":{"0":4,"1":4,"2":2,"3":2,"4":2,"5":2,"6":4,"7":4,"8":1,"9":3,"10":2,"11":2,"12":1,"13":1},"f":{"0":4,"1":4},"b":{"0":[2,2],"1":[2,1],"2":[2,1],"3":[1,3],"4":[2,1],"5":[2,1]},"meta":{"lastBranch":6,"lastFunction":2,"lastStatement":14,"seen":{"f:6:22:6:Infinity":0,"s:11:14:15:Infinity":0,"b:17:2:20:Infinity:undefined:undefined:undefined:undefined":0,"s:17:2:20:Infinity":1,"s:18:16:18:Infinity":2,"s:19:4:19:Infinity":3,"b:19:20:19:33:19:33:19:58":1,"s:22:15:22:Infinity":4,"s:23:2:23:Infinity":5,"b:23:31:23:45:23:45:23:50":2,"f:31:22:31:32":1,"s:32:14:32:Infinity":6,"b:34:2:36:Infinity:undefined:undefined:undefined:undefined":3,"s:34:2:36:Infinity":7,"s:35:4:35:Infinity":8,"b:38:2:41:Infinity:undefined:undefined:undefined:undefined":4,"s:38:2:41:Infinity":9,"s:39:16:39:Infinity":10,"s:40:4:40:Infinity":11,"b:40:20:40:33:40:33:40:60":5,"s:43:15:43:Infinity":12,"s:44:2:44:Infinity":13}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/lib/crypto.ts": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/lib/crypto.ts","statementMap":{"0":{"start":{"line":11,"column":16},"end":{"line":11,"column":null}},"1":{"start":{"line":12,"column":17},"end":{"line":12,"column":null}},"2":{"start":{"line":17,"column":2},"end":{"line":20,"column":null}},"3":{"start":{"line":24,"column":17},"end":{"line":24,"column":null}},"4":{"start":{"line":25,"column":17},"end":{"line":25,"column":null}},"5":{"start":{"line":26,"column":2},"end":{"line":26,"column":null}},"6":{"start":{"line":26,"column":46},"end":{"line":26,"column":61}},"7":{"start":{"line":33,"column":2},"end":{"line":36,"column":null}},"8":{"start":{"line":41,"column":14},"end":{"line":41,"column":null}},"9":{"start":{"line":42,"column":2},"end":{"line":42,"column":null}},"10":{"start":{"line":47,"column":14},"end":{"line":47,"column":null}},"11":{"start":{"line":48,"column":2},"end":{"line":54,"column":null}},"12":{"start":{"line":69,"column":17},"end":{"line":69,"column":null}},"13":{"start":{"line":71,"column":18},"end":{"line":77,"column":null}},"14":{"start":{"line":79,"column":18},"end":{"line":79,"column":null}},"15":{"start":{"line":80,"column":2},"end":{"line":91,"column":null}},"16":{"start":{"line":107,"column":21},"end":{"line":107,"column":null}},"17":{"start":{"line":108,"column":13},"end":{"line":108,"column":null}},"18":{"start":{"line":109,"column":14},"end":{"line":109,"column":null}},"19":{"start":{"line":110,"column":18},"end":{"line":110,"column":null}},"20":{"start":{"line":112,"column":21},"end":{"line":116,"column":null}},"21":{"start":{"line":119,"column":2},"end":{"line":119,"column":null}},"22":{"start":{"line":122,"column":18},"end":{"line":122,"column":null}},"23":{"start":{"line":123,"column":19},"end":{"line":123,"column":null}},"24":{"start":{"line":124,"column":2},"end":{"line":124,"column":null}},"25":{"start":{"line":125,"column":2},"end":{"line":125,"column":null}},"26":{"start":{"line":126,"column":2},"end":{"line":126,"column":null}},"27":{"start":{"line":128,"column":2},"end":{"line":128,"column":null}},"28":{"start":{"line":141,"column":19},"end":{"line":143,"column":null}},"29":{"start":{"line":142,"column":4},"end":{"line":142,"column":null}},"30":{"start":{"line":145,"column":18},"end":{"line":145,"column":null}},"31":{"start":{"line":146,"column":2},"end":{"line":148,"column":null}},"32":{"start":{"line":147,"column":4},"end":{"line":147,"column":null}},"33":{"start":{"line":150,"column":13},"end":{"line":150,"column":null}},"34":{"start":{"line":151,"column":21},"end":{"line":151,"column":null}},"35":{"start":{"line":152,"column":14},"end":{"line":152,"column":null}},"36":{"start":{"line":154,"column":21},"end":{"line":154,"column":null}},"37":{"start":{"line":156,"column":20},"end":{"line":160,"column":null}},"38":{"start":{"line":162,"column":2},"end":{"line":162,"column":null}}},"fnMap":{"0":{"name":"base64urlEncode","decl":{"start":{"line":16,"column":9},"end":{"line":16,"column":25}},"loc":{"start":{"line":16,"column":52},"end":{"line":21,"column":null}},"line":16},"1":{"name":"base64urlDecode","decl":{"start":{"line":23,"column":9},"end":{"line":23,"column":25}},"loc":{"start":{"line":23,"column":50},"end":{"line":27,"column":null}},"line":23},"2":{"name":"(anonymous_2)","decl":{"start":{"line":26,"column":39},"end":{"line":26,"column":40}},"loc":{"start":{"line":26,"column":46},"end":{"line":26,"column":61}},"line":26},"3":{"name":"generateKey","decl":{"start":{"line":32,"column":22},"end":{"line":32,"column":56}},"loc":{"start":{"line":32,"column":56},"end":{"line":37,"column":null}},"line":32},"4":{"name":"exportKey","decl":{"start":{"line":40,"column":22},"end":{"line":40,"column":32}},"loc":{"start":{"line":40,"column":65},"end":{"line":43,"column":null}},"line":40},"5":{"name":"importKey","decl":{"start":{"line":46,"column":22},"end":{"line":46,"column":32}},"loc":{"start":{"line":46,"column":71},"end":{"line":55,"column":null}},"line":46},"6":{"name":"deriveKey","decl":{"start":{"line":64,"column":15},"end":{"line":64,"column":null}},"loc":{"start":{"line":67,"column":22},"end":{"line":92,"column":null}},"line":67},"7":{"name":"encrypt","decl":{"start":{"line":102,"column":22},"end":{"line":102,"column":null}},"loc":{"start":{"line":106,"column":19},"end":{"line":129,"column":null}},"line":106},"8":{"name":"decrypt","decl":{"start":{"line":136,"column":22},"end":{"line":136,"column":null}},"loc":{"start":{"line":140,"column":19},"end":{"line":163,"column":null}},"line":140},"9":{"name":"(anonymous_9)","decl":{"start":{"line":141,"column":59},"end":{"line":141,"column":60}},"loc":{"start":{"line":142,"column":4},"end":{"line":142,"column":null}},"line":142}},"branchMap":{"0":{"loc":{"start":{"line":146,"column":2},"end":{"line":148,"column":null}},"type":"if","locations":[{"start":{"line":146,"column":2},"end":{"line":148,"column":null}},{"start":{},"end":{}}],"line":146}},"s":{"0":1,"1":1,"2":2,"3":1,"4":1,"5":1,"6":32,"7":13,"8":2,"9":2,"10":1,"11":1,"12":15,"13":15,"14":15,"15":15,"16":10,"17":10,"18":10,"19":10,"20":10,"21":10,"22":10,"23":10,"24":10,"25":10,"26":10,"27":10,"28":6,"29":226,"30":6,"31":6,"32":1,"33":5,"34":5,"35":5,"36":5,"37":5,"38":3},"f":{"0":2,"1":1,"2":32,"3":13,"4":2,"5":1,"6":15,"7":10,"8":6,"9":226},"b":{"0":[1,5]},"meta":{"lastBranch":1,"lastFunction":10,"lastStatement":39,"seen":{"s:11:16:11:Infinity":0,"s:12:17:12:Infinity":1,"f:16:9:16:25":0,"s:17:2:20:Infinity":2,"f:23:9:23:25":1,"s:24:17:24:Infinity":3,"s:25:17:25:Infinity":4,"s:26:2:26:Infinity":5,"f:26:39:26:40":2,"s:26:46:26:61":6,"f:32:22:32:56":3,"s:33:2:36:Infinity":7,"f:40:22:40:32":4,"s:41:14:41:Infinity":8,"s:42:2:42:Infinity":9,"f:46:22:46:32":5,"s:47:14:47:Infinity":10,"s:48:2:54:Infinity":11,"f:64:15:64:Infinity":6,"s:69:17:69:Infinity":12,"s:71:18:77:Infinity":13,"s:79:18:79:Infinity":14,"s:80:2:91:Infinity":15,"f:102:22:102:Infinity":7,"s:107:21:107:Infinity":16,"s:108:13:108:Infinity":17,"s:109:14:109:Infinity":18,"s:110:18:110:Infinity":19,"s:112:21:116:Infinity":20,"s:119:2:119:Infinity":21,"s:122:18:122:Infinity":22,"s:123:19:123:Infinity":23,"s:124:2:124:Infinity":24,"s:125:2:125:Infinity":25,"s:126:2:126:Infinity":26,"s:128:2:128:Infinity":27,"f:136:22:136:Infinity":8,"s:141:19:143:Infinity":28,"f:141:59:141:60":9,"s:142:4:142:Infinity":29,"s:145:18:145:Infinity":30,"b:146:2:148:Infinity:undefined:undefined:undefined:undefined":0,"s:146:2:148:Infinity":31,"s:147:4:147:Infinity":32,"s:150:13:150:Infinity":33,"s:151:21:151:Infinity":34,"s:152:14:152:Infinity":35,"s:154:21:154:Infinity":36,"s:156:20:160:Infinity":37,"s:162:2:162:Infinity":38}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/pages/CreateSecret.tsx": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/pages/CreateSecret.tsx","statementMap":{"0":{"start":{"line":17,"column":20},"end":{"line":24,"column":null}},"1":{"start":{"line":27,"column":18},"end":{"line":27,"column":null}},"2":{"start":{"line":28,"column":26},"end":{"line":28,"column":null}},"3":{"start":{"line":29,"column":30},"end":{"line":29,"column":null}},"4":{"start":{"line":30,"column":22},"end":{"line":30,"column":null}},"5":{"start":{"line":31,"column":28},"end":{"line":31,"column":null}},"6":{"start":{"line":32,"column":24},"end":{"line":32,"column":null}},"7":{"start":{"line":33,"column":26},"end":{"line":33,"column":null}},"8":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"9":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"10":{"start":{"line":37,"column":24},"end":{"line":37,"column":null}},"11":{"start":{"line":39,"column":4},"end":{"line":39,"column":null}},"12":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"13":{"start":{"line":41,"column":4},"end":{"line":41,"column":null}},"14":{"start":{"line":43,"column":4},"end":{"line":56,"column":null}},"15":{"start":{"line":44,"column":17},"end":{"line":44,"column":null}},"16":{"start":{"line":45,"column":18},"end":{"line":45,"column":null}},"17":{"start":{"line":46,"column":25},"end":{"line":46,"column":null}},"18":{"start":{"line":47,"column":21},"end":{"line":47,"column":null}},"19":{"start":{"line":48,"column":21},"end":{"line":48,"column":null}},"20":{"start":{"line":49,"column":21},"end":{"line":49,"column":null}},"21":{"start":{"line":50,"column":6},"end":{"line":50,"column":null}},"22":{"start":{"line":51,"column":6},"end":{"line":51,"column":null}},"23":{"start":{"line":53,"column":6},"end":{"line":53,"column":null}},"24":{"start":{"line":55,"column":6},"end":{"line":55,"column":null}},"25":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"26":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"27":{"start":{"line":62,"column":4},"end":{"line":62,"column":null}},"28":{"start":{"line":62,"column":21},"end":{"line":62,"column":39}},"29":{"start":{"line":66,"column":4},"end":{"line":66,"column":null}},"30":{"start":{"line":67,"column":4},"end":{"line":67,"column":null}},"31":{"start":{"line":71,"column":17},"end":{"line":71,"column":null}},"32":{"start":{"line":72,"column":4},"end":{"line":72,"column":null}},"33":{"start":{"line":76,"column":20},"end":{"line":76,"column":null}},"34":{"start":{"line":77,"column":17},"end":{"line":77,"column":null}},"35":{"start":{"line":78,"column":4},"end":{"line":78,"column":null}},"36":{"start":{"line":81,"column":2},"end":{"line":209,"column":null}},"37":{"start":{"line":99,"column":33},"end":{"line":99,"column":null}},"38":{"start":{"line":117,"column":18},"end":{"line":125,"column":null}},"39":{"start":{"line":122,"column":35},"end":{"line":122,"column":null}}},"fnMap":{"0":{"name":"CreateSecret","decl":{"start":{"line":26,"column":24},"end":{"line":26,"column":39}},"loc":{"start":{"line":26,"column":39},"end":{"line":211,"column":null}},"line":26},"1":{"name":"handleSubmit","decl":{"start":{"line":35,"column":17},"end":{"line":35,"column":30}},"loc":{"start":{"line":35,"column":50},"end":{"line":57,"column":null}},"line":35},"2":{"name":"handleCopy","decl":{"start":{"line":59,"column":17},"end":{"line":59,"column":30}},"loc":{"start":{"line":59,"column":30},"end":{"line":63,"column":null}},"line":59},"3":{"name":"(anonymous_3)","decl":{"start":{"line":62,"column":15},"end":{"line":62,"column":21}},"loc":{"start":{"line":62,"column":21},"end":{"line":62,"column":39}},"line":62},"4":{"name":"handleReset","decl":{"start":{"line":65,"column":11},"end":{"line":65,"column":25}},"loc":{"start":{"line":65,"column":25},"end":{"line":68,"column":null}},"line":65},"5":{"name":"whatsappUrl","decl":{"start":{"line":70,"column":11},"end":{"line":70,"column":25}},"loc":{"start":{"line":70,"column":25},"end":{"line":73,"column":null}},"line":70},"6":{"name":"mailtoUrl","decl":{"start":{"line":75,"column":11},"end":{"line":75,"column":23}},"loc":{"start":{"line":75,"column":23},"end":{"line":79,"column":null}},"line":75},"7":{"name":"(anonymous_7)","decl":{"start":{"line":99,"column":26},"end":{"line":99,"column":27}},"loc":{"start":{"line":99,"column":33},"end":{"line":99,"column":null}},"line":99},"8":{"name":"(anonymous_8)","decl":{"start":{"line":116,"column":33},"end":{"line":116,"column":34}},"loc":{"start":{"line":117,"column":18},"end":{"line":125,"column":null}},"line":117},"9":{"name":"(anonymous_9)","decl":{"start":{"line":122,"column":29},"end":{"line":122,"column":35}},"loc":{"start":{"line":122,"column":35},"end":{"line":122,"column":null}},"line":122}},"branchMap":{"0":{"loc":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"1":{"loc":{"start":{"line":49,"column":21},"end":{"line":49,"column":null}},"type":"binary-expr","locations":[{"start":{"line":49,"column":21},"end":{"line":49,"column":37}},{"start":{"line":49,"column":37},"end":{"line":49,"column":null}}],"line":49},"2":{"loc":{"start":{"line":53,"column":15},"end":{"line":53,"column":74}},"type":"cond-expr","locations":[{"start":{"line":53,"column":38},"end":{"line":53,"column":52}},{"start":{"line":53,"column":52},"end":{"line":53,"column":74}}],"line":53},"3":{"loc":{"start":{"line":89,"column":9},"end":{"line":206,"column":null}},"type":"cond-expr","locations":[{"start":{"line":90,"column":10},"end":{"line":154,"column":null}},{"start":{"line":156,"column":10},"end":{"line":206,"column":null}}],"line":89},"4":{"loc":{"start":{"line":130,"column":13},"end":{"line":134,"column":null}},"type":"binary-expr","locations":[{"start":{"line":130,"column":13},"end":{"line":130,"column":null}},{"start":{"line":131,"column":14},"end":{"line":134,"column":null}}],"line":130},"5":{"loc":{"start":{"line":140,"column":24},"end":{"line":140,"column":null}},"type":"binary-expr","locations":[{"start":{"line":140,"column":24},"end":{"line":140,"column":35}},{"start":{"line":140,"column":35},"end":{"line":140,"column":null}}],"line":140},"6":{"loc":{"start":{"line":142,"column":15},"end":{"line":151,"column":null}},"type":"cond-expr","locations":[{"start":{"line":143,"column":16},"end":{"line":146,"column":null}},{"start":{"line":148,"column":16},"end":{"line":151,"column":null}}],"line":142},"7":{"loc":{"start":{"line":171,"column":40},"end":{"line":171,"column":73}},"type":"cond-expr","locations":[{"start":{"line":171,"column":49},"end":{"line":171,"column":71}},{"start":{"line":171,"column":71},"end":{"line":171,"column":73}}],"line":171},"8":{"loc":{"start":{"line":175,"column":17},"end":{"line":175,"column":null}},"type":"cond-expr","locations":[{"start":{"line":175,"column":26},"end":{"line":175,"column":48}},{"start":{"line":175,"column":48},"end":{"line":175,"column":null}}],"line":175},"9":{"loc":{"start":{"line":176,"column":23},"end":{"line":176,"column":70}},"type":"cond-expr","locations":[{"start":{"line":176,"column":32},"end":{"line":176,"column":53}},{"start":{"line":176,"column":53},"end":{"line":176,"column":70}}],"line":176}},"s":{"0":1,"1":75,"2":75,"3":75,"4":75,"5":75,"6":75,"7":75,"8":8,"9":8,"10":0,"11":8,"12":8,"13":8,"14":8,"15":8,"16":8,"17":8,"18":7,"19":6,"20":6,"21":8,"22":8,"23":2,"24":8,"25":1,"26":1,"27":1,"28":0,"29":1,"30":1,"31":7,"32":7,"33":7,"34":7,"35":7,"36":75,"37":42,"38":408,"39":1},"f":{"0":75,"1":8,"2":1,"3":0,"4":1,"5":7,"6":7,"7":42,"8":408,"9":1},"b":{"0":[0,8],"1":[6,0],"2":[1,1],"3":[68,7],"4":[68,2],"5":[68,60],"6":[8,60],"7":[1,6],"8":[1,6],"9":[1,6]},"meta":{"lastBranch":10,"lastFunction":10,"lastStatement":40,"seen":{"s:17:20:24:Infinity":0,"f:26:24:26:39":0,"s:27:18:27:Infinity":1,"s:28:26:28:Infinity":2,"s:29:30:29:Infinity":3,"s:30:22:30:Infinity":4,"s:31:28:31:Infinity":5,"s:32:24:32:Infinity":6,"s:33:26:33:Infinity":7,"f:35:17:35:30":1,"s:36:4:36:Infinity":8,"b:37:4:37:Infinity:undefined:undefined:undefined:undefined":0,"s:37:4:37:Infinity":9,"s:37:24:37:Infinity":10,"s:39:4:39:Infinity":11,"s:40:4:40:Infinity":12,"s:41:4:41:Infinity":13,"s:43:4:56:Infinity":14,"s:44:17:44:Infinity":15,"s:45:18:45:Infinity":16,"s:46:25:46:Infinity":17,"s:47:21:47:Infinity":18,"s:48:21:48:Infinity":19,"s:49:21:49:Infinity":20,"b:49:21:49:37:49:37:49:Infinity":1,"s:50:6:50:Infinity":21,"s:51:6:51:Infinity":22,"s:53:6:53:Infinity":23,"b:53:38:53:52:53:52:53:74":2,"s:55:6:55:Infinity":24,"f:59:17:59:30":2,"s:60:4:60:Infinity":25,"s:61:4:61:Infinity":26,"s:62:4:62:Infinity":27,"f:62:15:62:21":3,"s:62:21:62:39":28,"f:65:11:65:25":4,"s:66:4:66:Infinity":29,"s:67:4:67:Infinity":30,"f:70:11:70:25":5,"s:71:17:71:Infinity":31,"s:72:4:72:Infinity":32,"f:75:11:75:23":6,"s:76:20:76:Infinity":33,"s:77:17:77:Infinity":34,"s:78:4:78:Infinity":35,"s:81:2:209:Infinity":36,"b:90:10:154:Infinity:156:10:206:Infinity":3,"f:99:26:99:27":7,"s:99:33:99:Infinity":37,"f:116:33:116:34":8,"s:117:18:125:Infinity":38,"f:122:29:122:35":9,"s:122:35:122:Infinity":39,"b:130:13:130:Infinity:131:14:134:Infinity":4,"b:140:24:140:35:140:35:140:Infinity":5,"b:143:16:146:Infinity:148:16:151:Infinity":6,"b:171:49:171:71:171:71:171:73":7,"b:175:26:175:48:175:48:175:Infinity":8,"b:176:32:176:53:176:53:176:70":9}}} -,"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/pages/ViewSecret.tsx": {"path":"/Users/diogo/github-archive/repos/dhdtech/only-once-share/ui/src/pages/ViewSecret.tsx","statementMap":{"0":{"start":{"line":18,"column":12},"end":{"line":18,"column":null}},"1":{"start":{"line":19,"column":13},"end":{"line":19,"column":null}},"2":{"start":{"line":20,"column":26},"end":{"line":20,"column":null}},"3":{"start":{"line":21,"column":32},"end":{"line":21,"column":null}},"4":{"start":{"line":22,"column":24},"end":{"line":22,"column":null}},"5":{"start":{"line":23,"column":26},"end":{"line":23,"column":null}},"6":{"start":{"line":25,"column":2},"end":{"line":52,"column":null}},"7":{"start":{"line":27,"column":21},"end":{"line":27,"column":null}},"8":{"start":{"line":28,"column":6},"end":{"line":32,"column":null}},"9":{"start":{"line":29,"column":8},"end":{"line":29,"column":null}},"10":{"start":{"line":30,"column":8},"end":{"line":30,"column":null}},"11":{"start":{"line":31,"column":8},"end":{"line":31,"column":null}},"12":{"start":{"line":34,"column":6},"end":{"line":48,"column":null}},"13":{"start":{"line":35,"column":23},"end":{"line":35,"column":null}},"14":{"start":{"line":36,"column":20},"end":{"line":36,"column":null}},"15":{"start":{"line":37,"column":26},"end":{"line":37,"column":null}},"16":{"start":{"line":38,"column":8},"end":{"line":38,"column":null}},"17":{"start":{"line":39,"column":8},"end":{"line":39,"column":null}},"18":{"start":{"line":41,"column":20},"end":{"line":41,"column":null}},"19":{"start":{"line":42,"column":8},"end":{"line":47,"column":null}},"20":{"start":{"line":43,"column":10},"end":{"line":43,"column":null}},"21":{"start":{"line":45,"column":10},"end":{"line":45,"column":null}},"22":{"start":{"line":46,"column":10},"end":{"line":46,"column":null}},"23":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"24":{"start":{"line":55,"column":4},"end":{"line":55,"column":null}},"25":{"start":{"line":56,"column":4},"end":{"line":56,"column":null}},"26":{"start":{"line":57,"column":4},"end":{"line":57,"column":null}},"27":{"start":{"line":57,"column":21},"end":{"line":57,"column":39}},"28":{"start":{"line":60,"column":2},"end":{"line":119,"column":null}}},"fnMap":{"0":{"name":"ViewSecret","decl":{"start":{"line":17,"column":24},"end":{"line":17,"column":37}},"loc":{"start":{"line":17,"column":37},"end":{"line":121,"column":null}},"line":17},"1":{"name":"(anonymous_1)","decl":{"start":{"line":25,"column":12},"end":{"line":25,"column":18}},"loc":{"start":{"line":25,"column":18},"end":{"line":52,"column":5}},"line":25},"2":{"name":"fetchAndDecrypt","decl":{"start":{"line":26,"column":19},"end":{"line":26,"column":37}},"loc":{"start":{"line":26,"column":37},"end":{"line":49,"column":null}},"line":26},"3":{"name":"handleCopy","decl":{"start":{"line":54,"column":17},"end":{"line":54,"column":30}},"loc":{"start":{"line":54,"column":30},"end":{"line":58,"column":null}},"line":54},"4":{"name":"(anonymous_4)","decl":{"start":{"line":57,"column":15},"end":{"line":57,"column":21}},"loc":{"start":{"line":57,"column":21},"end":{"line":57,"column":39}},"line":57}},"branchMap":{"0":{"loc":{"start":{"line":28,"column":6},"end":{"line":32,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":6},"end":{"line":32,"column":null}},{"start":{},"end":{}}],"line":28},"1":{"loc":{"start":{"line":28,"column":10},"end":{"line":28,"column":26}},"type":"binary-expr","locations":[{"start":{"line":28,"column":10},"end":{"line":28,"column":21}},{"start":{"line":28,"column":21},"end":{"line":28,"column":26}}],"line":28},"2":{"loc":{"start":{"line":41,"column":20},"end":{"line":41,"column":null}},"type":"cond-expr","locations":[{"start":{"line":41,"column":43},"end":{"line":41,"column":57}},{"start":{"line":41,"column":57},"end":{"line":41,"column":null}}],"line":41},"3":{"loc":{"start":{"line":42,"column":8},"end":{"line":47,"column":null}},"type":"if","locations":[{"start":{"line":42,"column":8},"end":{"line":47,"column":null}},{"start":{"line":44,"column":15},"end":{"line":47,"column":null}}],"line":42},"4":{"loc":{"start":{"line":42,"column":12},"end":{"line":42,"column":73}},"type":"binary-expr","locations":[{"start":{"line":42,"column":12},"end":{"line":42,"column":41}},{"start":{"line":42,"column":41},"end":{"line":42,"column":73}}],"line":42},"5":{"loc":{"start":{"line":62,"column":7},"end":{"line":68,"column":null}},"type":"binary-expr","locations":[{"start":{"line":62,"column":7},"end":{"line":62,"column":null}},{"start":{"line":63,"column":8},"end":{"line":68,"column":null}}],"line":62},"6":{"loc":{"start":{"line":71,"column":7},"end":{"line":88,"column":null}},"type":"binary-expr","locations":[{"start":{"line":71,"column":7},"end":{"line":71,"column":null}},{"start":{"line":72,"column":8},"end":{"line":88,"column":null}}],"line":71},"7":{"loc":{"start":{"line":80,"column":48},"end":{"line":80,"column":88}},"type":"cond-expr","locations":[{"start":{"line":80,"column":57},"end":{"line":80,"column":73}},{"start":{"line":80,"column":73},"end":{"line":80,"column":88}}],"line":80},"8":{"loc":{"start":{"line":84,"column":15},"end":{"line":84,"column":null}},"type":"cond-expr","locations":[{"start":{"line":84,"column":24},"end":{"line":84,"column":46}},{"start":{"line":84,"column":46},"end":{"line":84,"column":null}}],"line":84},"9":{"loc":{"start":{"line":85,"column":15},"end":{"line":85,"column":null}},"type":"cond-expr","locations":[{"start":{"line":85,"column":24},"end":{"line":85,"column":52}},{"start":{"line":85,"column":52},"end":{"line":85,"column":null}}],"line":85},"10":{"loc":{"start":{"line":91,"column":7},"end":{"line":100,"column":null}},"type":"binary-expr","locations":[{"start":{"line":91,"column":7},"end":{"line":91,"column":null}},{"start":{"line":92,"column":8},"end":{"line":100,"column":null}}],"line":91},"11":{"loc":{"start":{"line":103,"column":7},"end":{"line":112,"column":null}},"type":"binary-expr","locations":[{"start":{"line":103,"column":7},"end":{"line":103,"column":null}},{"start":{"line":104,"column":8},"end":{"line":112,"column":null}}],"line":103},"12":{"loc":{"start":{"line":110,"column":16},"end":{"line":110,"column":44}},"type":"binary-expr","locations":[{"start":{"line":110,"column":16},"end":{"line":110,"column":25}},{"start":{"line":110,"column":25},"end":{"line":110,"column":44}}],"line":110},"13":{"loc":{"start":{"line":117,"column":9},"end":{"line":117,"column":null}},"type":"cond-expr","locations":[{"start":{"line":117,"column":33},"end":{"line":117,"column":55}},{"start":{"line":117,"column":55},"end":{"line":117,"column":null}}],"line":117}},"s":{"0":20,"1":20,"2":20,"3":20,"4":20,"5":20,"6":20,"7":10,"8":10,"9":2,"10":2,"11":2,"12":8,"13":8,"14":4,"15":4,"16":3,"17":3,"18":4,"19":4,"20":2,"21":2,"22":2,"23":10,"24":1,"25":1,"26":1,"27":0,"28":20},"f":{"0":20,"1":10,"2":10,"3":1,"4":0},"b":{"0":[2,8],"1":[10,9],"2":[3,1],"3":[2,2],"4":[4,2],"5":[20,10],"6":[20,4],"7":[1,3],"8":[1,3],"9":[1,3],"10":[20,2],"11":[20,4],"12":[4,0],"13":[4,16]},"meta":{"lastBranch":14,"lastFunction":5,"lastStatement":29,"seen":{"f:17:24:17:37":0,"s:18:12:18:Infinity":0,"s:19:13:19:Infinity":1,"s:20:26:20:Infinity":2,"s:21:32:21:Infinity":3,"s:22:24:22:Infinity":4,"s:23:26:23:Infinity":5,"s:25:2:52:Infinity":6,"f:25:12:25:18":1,"f:26:19:26:37":2,"s:27:21:27:Infinity":7,"b:28:6:32:Infinity:undefined:undefined:undefined:undefined":0,"s:28:6:32:Infinity":8,"b:28:10:28:21:28:21:28:26":1,"s:29:8:29:Infinity":9,"s:30:8:30:Infinity":10,"s:31:8:31:Infinity":11,"s:34:6:48:Infinity":12,"s:35:23:35:Infinity":13,"s:36:20:36:Infinity":14,"s:37:26:37:Infinity":15,"s:38:8:38:Infinity":16,"s:39:8:39:Infinity":17,"s:41:20:41:Infinity":18,"b:41:43:41:57:41:57:41:Infinity":2,"b:42:8:47:Infinity:44:15:47:Infinity":3,"s:42:8:47:Infinity":19,"b:42:12:42:41:42:41:42:73":4,"s:43:10:43:Infinity":20,"s:45:10:45:Infinity":21,"s:46:10:46:Infinity":22,"s:51:4:51:Infinity":23,"f:54:17:54:30":3,"s:55:4:55:Infinity":24,"s:56:4:56:Infinity":25,"s:57:4:57:Infinity":26,"f:57:15:57:21":4,"s:57:21:57:39":27,"s:60:2:119:Infinity":28,"b:62:7:62:Infinity:63:8:68:Infinity":5,"b:71:7:71:Infinity:72:8:88:Infinity":6,"b:80:57:80:73:80:73:80:88":7,"b:84:24:84:46:84:46:84:Infinity":8,"b:85:24:85:52:85:52:85:Infinity":9,"b:91:7:91:Infinity:92:8:100:Infinity":10,"b:103:7:103:Infinity:104:8:112:Infinity":11,"b:110:16:110:25:110:25:110:44":12,"b:117:33:117:55:117:55:117:Infinity":13}}} -} diff --git a/ui/coverage/favicon.png b/ui/coverage/favicon.png deleted file mode 100644 index c1525b8..0000000 Binary files a/ui/coverage/favicon.png and /dev/null differ diff --git a/ui/coverage/i18n/index.html b/ui/coverage/i18n/index.html deleted file mode 100644 index ebbb179..0000000 --- a/ui/coverage/i18n/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for i18n - - - - - - - - - -
-
-

All files i18n

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
index.ts -
-
100%3/3100%0/0100%1/1100%3/3
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/i18n/index.ts.html b/ui/coverage/i18n/index.ts.html deleted file mode 100644 index afd9017..0000000 --- a/ui/coverage/i18n/index.ts.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - Code coverage report for i18n/index.ts - - - - - - - - - -
-
-

All files / i18n index.ts

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -21x -  -  -  -  -  -6x -  -  -  -  -  -  -  - 
import i18n from "i18next";
-import { initReactI18next } from "react-i18next";
-import LanguageDetector from "i18next-browser-languagedetector";
- 
-import en from "./locales/en.json";
-import zh from "./locales/zh.json";
-import es from "./locales/es.json";
-import hi from "./locales/hi.json";
-import ar from "./locales/ar.json";
-import pt from "./locales/pt.json";
- 
-i18n
-  .use(LanguageDetector)
-  .use(initReactI18next)
-  .init({
-    resources: {
-      en: { translation: en },
-      zh: { translation: zh },
-      es: { translation: es },
-      hi: { translation: hi },
-      ar: { translation: ar },
-      pt: { translation: pt },
-    },
-    fallbackLng: "en",
-    interpolation: {
-      escapeValue: false,
-    },
-    detection: {
-      order: ["querystring", "localStorage", "navigator"],
-      caches: ["localStorage"],
-      lookupLocalStorage: "i18nextLng",
-      convertDetectedLanguage: (lng: string) => lng.split("-")[0],
-    },
-  });
- 
-export default i18n;
- 
-export const LANGUAGES = [
-  { code: "en", flag: "\u{1F1FA}\u{1F1F8}", label: "English" },
-  { code: "zh", flag: "\u{1F1E8}\u{1F1F3}", label: "\u4E2D\u6587" },
-  { code: "es", flag: "\u{1F1EA}\u{1F1F8}", label: "Espa\u00F1ol" },
-  { code: "hi", flag: "\u{1F1EE}\u{1F1F3}", label: "\u0939\u093F\u0928\u094D\u0926\u0940" },
-  { code: "ar", flag: "\u{1F1F8}\u{1F1E6}", label: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629" },
-  { code: "pt", flag: "\u{1F1E7}\u{1F1F7}", label: "Portugu\u00EAs" },
-];
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/index.html b/ui/coverage/index.html deleted file mode 100644 index df24577..0000000 --- a/ui/coverage/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 98.01% - Statements - 148/151 -
- - -
- 94.59% - Branches - 70/74 -
- - -
- 95.34% - Functions - 41/43 -
- - -
- 100% - Lines - 145/145 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
components -
-
100%26/2691.66%11/12100%15/15100%24/24
i18n -
-
100%3/3100%0/0100%1/1100%3/3
lib -
-
100%53/53100%14/14100%12/12100%52/52
pages -
-
95.65%66/6993.75%45/4886.66%13/15100%66/66
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/lib/api.ts.html b/ui/coverage/lib/api.ts.html deleted file mode 100644 index c86070c..0000000 --- a/ui/coverage/lib/api.ts.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - Code coverage report for lib/api.ts - - - - - - - - - -
-
-

All files / lib api.ts

-
- -
- 100% - Statements - 14/14 -
- - -
- 100% - Branches - 12/12 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 14/14 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46  -  -  -  -  -  -  -  -  -  -4x -  -  -  -  -  -4x -2x -2x -  -  -2x -2x -  -  -  -  -  -  -  -  -4x -  -4x -1x -  -  -3x -2x -2x -  -  -1x -1x -  - 
export interface CreateSecretResult {
-  id: string;
-  alias: string | null;
-}
- 
-export async function createSecret(
-  ciphertext: string,
-  ttlHours: number,
-  id: string,
-): Promise<CreateSecretResult> {
-  const res = await fetch("/api/secrets", {
-    method: "POST",
-    headers: { "Content-Type": "application/json" },
-    body: JSON.stringify({ ciphertext, ttl_hours: ttlHours, id }),
-  });
- 
-  if (!res.ok) {
-    const err = await res.json();
-    throw new Error(err.error || "Failed to create secret");
-  }
- 
-  const data = await res.json();
-  return { id: data.id, alias: data.alias ?? null };
-}
- 
-export interface GetSecretResult {
-  ciphertext: string;
-  id: string;
-}
- 
-export async function getSecret(id: string): Promise<GetSecretResult> {
-  const res = await fetch(`/api/secrets/${id}`);
- 
-  if (res.status === 404) {
-    throw new Error("Secret not found or already viewed");
-  }
- 
-  if (!res.ok) {
-    const err = await res.json();
-    throw new Error(err.error || "Failed to retrieve secret");
-  }
- 
-  const data = await res.json();
-  return { ciphertext: data.ciphertext, id: data.id };
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/lib/crypto.ts.html b/ui/coverage/lib/crypto.ts.html deleted file mode 100644 index 05a8cea..0000000 --- a/ui/coverage/lib/crypto.ts.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - Code coverage report for lib/crypto.ts - - - - - - - - - -
-
-

All files / lib crypto.ts

-
- -
- 100% - Statements - 39/39 -
- - -
- 100% - Branches - 2/2 -
- - -
- 100% - Functions - 10/10 -
- - -
- 100% - Lines - 38/38 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164  -  -  -  -  -  -  -  -  -  -1x -1x -  -  -  -  -2x -  -  -  -  -  -  -1x -1x -32x -  -  -  -  -  -  -13x -  -  -  -  -  -  -  -2x -2x -  -  -  -  -1x -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -15x -  -15x -  -  -  -  -  -  -  -15x -15x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -10x -10x -10x -10x -  -10x -  -  -  -  -  -  -10x -  -  -10x -10x -10x -10x -10x -  -10x -  -  -  -  -  -  -  -  -  -  -  -  -6x -226x -  -  -6x -6x -1x -  -  -5x -5x -5x -  -5x -  -5x -  -  -  -  -  -3x -  - 
/**
- * Only Once Share — Client-Side Encryption Module
- *
- * Cipher:   AES-256-GCM (authenticated encryption)
- * KDF:      HKDF-SHA-256 (derives per-secret key from master key + secret ID)
- * IV:       96-bit random per encryption (NIST recommended)
- * AAD:      Secret ID bound as additional authenticated data
- * Format:   [version 1B] [iv 12B] [ciphertext + GCM tag]
- */
- 
-const VERSION = 0x01;
-const IV_BYTES = 12;
- 
-// --------------- base64url helpers ---------------
- 
-function base64urlEncode(bytes: Uint8Array): string {
-  return btoa(String.fromCharCode(...bytes))
-    .replace(/\+/g, "-")
-    .replace(/\//g, "_")
-    .replace(/=+$/, "");
-}
- 
-function base64urlDecode(str: string): Uint8Array {
-  const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
-  const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
-  return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
-}
- 
-// --------------- key management ---------------
- 
-/** Generate a 256-bit master key (stored in URL fragment). */
-export async function generateKey(): Promise<CryptoKey> {
-  return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [
-    "encrypt",
-    "decrypt",
-  ]);
-}
- 
-/** Export master key to base64url string for the URL fragment. */
-export async function exportKey(key: CryptoKey): Promise<string> {
-  const raw = await crypto.subtle.exportKey("raw", key);
-  return base64urlEncode(new Uint8Array(raw));
-}
- 
-/** Import master key from base64url string. */
-export async function importKey(base64url: string): Promise<CryptoKey> {
-  const raw = base64urlDecode(base64url);
-  return crypto.subtle.importKey(
-    "raw",
-    raw,
-    { name: "AES-GCM", length: 256 },
-    true,
-    ["encrypt", "decrypt"],
-  );
-}
- 
-// --------------- HKDF key derivation ---------------
- 
-/**
- * Derive a per-secret AES-256-GCM key from the master key using HKDF-SHA-256.
- * The secret ID is used as the `info` parameter, binding the derived key
- * to this specific secret. An attacker cannot reuse ciphertext across secrets.
- */
-async function deriveKey(
-  masterKey: CryptoKey,
-  secretId: string,
-): Promise<CryptoKey> {
-  // Export master key raw bytes to use as HKDF input keying material
-  const rawKey = await crypto.subtle.exportKey("raw", masterKey);
- 
-  const hkdfKey = await crypto.subtle.importKey(
-    "raw",
-    rawKey,
-    { name: "HKDF" },
-    false,
-    ["deriveKey"],
-  );
- 
-  const encoder = new TextEncoder();
-  return crypto.subtle.deriveKey(
-    {
-      name: "HKDF",
-      hash: "SHA-256",
-      salt: encoder.encode("only-once-share-v1"),
-      info: encoder.encode(secretId),
-    },
-    hkdfKey,
-    { name: "AES-GCM", length: 256 },
-    false,
-    ["encrypt", "decrypt"],
-  );
-}
- 
-// --------------- encrypt / decrypt ---------------
- 
-/**
- * Encrypt plaintext with AES-256-GCM.
- * - Derives a unique key via HKDF(masterKey, secretId)
- * - Uses the secret ID as Additional Authenticated Data (AAD)
- * - Output: base64( version || iv || ciphertext+tag )
- */
-export async function encrypt(
-  plaintext: string,
-  masterKey: CryptoKey,
-  secretId: string,
-): Promise<string> {
-  const derivedKey = await deriveKey(masterKey, secretId);
-  const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
-  const aad = new TextEncoder().encode(secretId);
-  const encoded = new TextEncoder().encode(plaintext);
- 
-  const ciphertext = await crypto.subtle.encrypt(
-    { name: "AES-GCM", iv, additionalData: aad },
-    derivedKey,
-    encoded,
-  );
- 
-  // Wipe plaintext from memory
-  encoded.fill(0);
- 
-  // Assemble: [version 1B] [iv 12B] [ciphertext+tag]
-  const ctBytes = new Uint8Array(ciphertext);
-  const combined = new Uint8Array(1 + IV_BYTES + ctBytes.length);
-  combined[0] = VERSION;
-  combined.set(iv, 1);
-  combined.set(ctBytes, 1 + IV_BYTES);
- 
-  return btoa(String.fromCharCode(...combined));
-}
- 
-/**
- * Decrypt ciphertext with AES-256-GCM.
- * - Derives the same key via HKDF(masterKey, secretId)
- * - Verifies AAD matches the secret ID (tamper detection)
- */
-export async function decrypt(
-  base64Ciphertext: string,
-  masterKey: CryptoKey,
-  secretId: string,
-): Promise<string> {
-  const combined = Uint8Array.from(atob(base64Ciphertext), (c) =>
-    c.charCodeAt(0),
-  );
- 
-  const version = combined[0];
-  if (version !== VERSION) {
-    throw new Error("Unsupported encryption version");
-  }
- 
-  const iv = combined.slice(1, 1 + IV_BYTES);
-  const ciphertext = combined.slice(1 + IV_BYTES);
-  const aad = new TextEncoder().encode(secretId);
- 
-  const derivedKey = await deriveKey(masterKey, secretId);
- 
-  const decrypted = await crypto.subtle.decrypt(
-    { name: "AES-GCM", iv, additionalData: aad },
-    derivedKey,
-    ciphertext,
-  );
- 
-  return new TextDecoder().decode(decrypted);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/lib/index.html b/ui/coverage/lib/index.html deleted file mode 100644 index 972999a..0000000 --- a/ui/coverage/lib/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for lib - - - - - - - - - -
-
-

All files lib

-
- -
- 100% - Statements - 53/53 -
- - -
- 100% - Branches - 14/14 -
- - -
- 100% - Functions - 12/12 -
- - -
- 100% - Lines - 52/52 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
api.ts -
-
100%14/14100%12/12100%2/2100%14/14
crypto.ts -
-
100%39/39100%2/2100%10/10100%38/38
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/pages/CreateSecret.tsx.html b/ui/coverage/pages/CreateSecret.tsx.html deleted file mode 100644 index 81bd2c5..0000000 --- a/ui/coverage/pages/CreateSecret.tsx.html +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - Code coverage report for pages/CreateSecret.tsx - - - - - - - - - -
-
-

All files / pages CreateSecret.tsx

-
- -
- 95% - Statements - 38/40 -
- - -
- 90% - Branches - 18/20 -
- - -
- 90% - Functions - 9/10 -
- - -
- 100% - Lines - 38/38 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -75x -75x -75x -75x -75x -75x -75x -  -  -8x -8x -  -8x -8x -8x -  -8x -8x -8x -8x -7x -6x -6x -8x -8x -  -2x -  -8x -  -  -  -  -1x -1x -1x -  -  -  -1x -1x -  -  -  -7x -7x -  -  -  -7x -7x -7x -  -  -75x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -42x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -408x -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useState } from "react";
-import { useTranslation } from "react-i18next";
-import {
-  Lock,
-  Clock,
-  Copy,
-  Check,
-  Plus,
-  AlertCircle,
-  CheckCircle2,
-  Loader2,
-  Mail,
-} from "lucide-react";
-import { generateKey, exportKey, encrypt } from "../lib/crypto";
-import { createSecret } from "../lib/api";
- 
-const TTL_OPTIONS = [
-  { value: 1, label: "1h" },
-  { value: 4, label: "4h" },
-  { value: 12, label: "12h" },
-  { value: 24, label: "24h" },
-  { value: 48, label: "48h" },
-  { value: 72, label: "72h" },
-];
- 
-export default function CreateSecret() {
-  const { t, i18n } = useTranslation();
-  const [secret, setSecret] = useState("");
-  const [ttlHours, setTtlHours] = useState(24);
-  const [link, setLink] = useState("");
-  const [loading, setLoading] = useState(false);
-  const [error, setError] = useState("");
-  const [copied, setCopied] = useState(false);
- 
-  async function handleSubmit(e: React.FormEvent) {
-    e.preventDefault();
-    Iif (!secret.trim()) return;
- 
-    setLoading(true);
-    setError("");
-    setLink("");
- 
-    try {
-      const id = crypto.randomUUID();
-      const key = await generateKey();
-      const ciphertext = await encrypt(secret, key, id);
-      const result = await createSecret(ciphertext, ttlHours, id);
-      const keyStr = await exportKey(key);
-      const pathId = result.alias ?? result.id;
-      setLink(`${window.location.origin}/s/${pathId}?lng=${i18n.language}#${keyStr}`);
-      setSecret("");
-    } catch (err) {
-      setError(err instanceof Error ? err.message : "Something went wrong");
-    } finally {
-      setLoading(false);
-    }
-  }
- 
-  async function handleCopy() {
-    await navigator.clipboard.writeText(link);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
-  }
- 
-  function handleReset() {
-    setLink("");
-    setCopied(false);
-  }
- 
-  function whatsappUrl() {
-    const text = t("create.whatsappMsg", { link });
-    return `https://wa.me/?text=${encodeURIComponent(text)}`;
-  }
- 
-  function mailtoUrl() {
-    const subject = t("create.emailSubject");
-    const body = t("create.emailBody", { link });
-    return `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
-  }
- 
-  return (
-    <>
-      <div className="hero">
-        <h1 className="hero-title">{t("hero.title")}</h1>
-        <p className="hero-subtitle">{t("hero.subtitle")}</p>
-      </div>
- 
-      <div className="card">
-        {!link ? (
-          <form className="form" onSubmit={handleSubmit}>
-            <div className="form-group">
-              <label className="form-label" htmlFor="secret-input">
-                <Lock size={14} />
-                {t("create.label")}
-              </label>
-              <textarea
-                id="secret-input"
-                value={secret}
-                onChange={(e) => setSecret(e.target.value)}
-                placeholder={t("create.placeholder")}
-                rows={6}
-                maxLength={50000}
-                required
-              />
-              <span className="char-count">
-                {secret.length.toLocaleString()} / 50,000
-              </span>
-            </div>
- 
-            <div className="ttl-group">
-              <span className="form-label">
-                <Clock size={14} />
-                {t("create.expiresIn")}
-              </span>
-              <div className="ttl-options" role="group" aria-label={t("create.expiresIn")}>
-                {TTL_OPTIONS.map((opt) => (
-                  <button
-                    key={opt.value}
-                    type="button"
-                    className="ttl-option"
-                    aria-pressed={ttlHours === opt.value}
-                    onClick={() => setTtlHours(opt.value)}
-                  >
-                    {opt.label}
-                  </button>
-                ))}
-              </div>
-            </div>
- 
-            {error && (
-              <div className="error-msg">
-                <AlertCircle size={15} />
-                <span>{error}</span>
-              </div>
-            )}
- 
-            <button
-              type="submit"
-              className="btn btn-primary btn-full"
-              disabled={loading || !secret.trim()}
-            >
-              {loading ? (
-                <>
-                  <Loader2 size={16} className="spinning" />
-                  {t("create.encrypting")}
-                </>
-              ) : (
-                <>
-                  <Lock size={16} />
-                  {t("create.submit")}
-                </>
-              )}
-            </button>
-          </form>
-        ) : (
-          <div className="result">
-            <div className="result-header">
-              <CheckCircle2 size={18} />
-              <span>{t("create.linkCreated")}</span>
-            </div>
- 
-            <p className="result-info">{t("create.linkInfo")}</p>
- 
-            <div className="link-box">
-              <div className="link-display">{link}</div>
-            </div>
- 
-            <div className="share-label">{t("create.shareVia")}</div>
-            <div className="share-buttons">
-              <button
-                className={`share-btn ${copied ? "share-btn--copied" : ""}`}
-                onClick={handleCopy}
-                aria-label={t("create.copy")}
-              >
-                {copied ? <Check size={17} /> : <Copy size={17} />}
-                <span>{copied ? t("create.copied") : t("create.copy")}</span>
-              </button>
- 
-              <a
-                className="share-btn share-btn--whatsapp"
-                href={whatsappUrl()}
-                target="_blank"
-                rel="noopener noreferrer"
-                aria-label={t("create.whatsapp")}
-              >
-                <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
-                  <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" />
-                </svg>
-                <span>{t("create.whatsapp")}</span>
-              </a>
- 
-              <a
-                className="share-btn share-btn--email"
-                href={mailtoUrl()}
-                aria-label={t("create.email")}
-              >
-                <Mail size={17} />
-                <span>{t("create.email")}</span>
-              </a>
-            </div>
- 
-            <button className="btn btn-secondary btn-full" onClick={handleReset}>
-              <Plus size={16} />
-              {t("create.createAnother")}
-            </button>
-          </div>
-        )}
-      </div>
-    </>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/pages/ViewSecret.tsx.html b/ui/coverage/pages/ViewSecret.tsx.html deleted file mode 100644 index 31a4e68..0000000 --- a/ui/coverage/pages/ViewSecret.tsx.html +++ /dev/null @@ -1,448 +0,0 @@ - - - - - - Code coverage report for pages/ViewSecret.tsx - - - - - - - - - -
-
-

All files / pages ViewSecret.tsx

-
- -
- 96.55% - Statements - 28/29 -
- - -
- 96.42% - Branches - 27/28 -
- - -
- 80% - Functions - 4/5 -
- - -
- 100% - Lines - 28/28 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -20x -20x -20x -20x -20x -20x -  -20x -  -10x -10x -2x -2x -2x -  -  -8x -8x -4x -4x -3x -3x -  -4x -4x -2x -  -2x -2x -  -  -  -  -10x -  -  -  -1x -1x -1x -  -  -20x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { useEffect, useState } from "react";
-import { useParams } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import {
-  ShieldOff,
-  ArrowLeft,
-  EyeOff,
-  AlertCircle,
-  Copy,
-  Check,
-} from "lucide-react";
-import { importKey, decrypt } from "../lib/crypto";
-import { getSecret } from "../lib/api";
- 
-type Status = "loading" | "revealed" | "not-found" | "error";
- 
-export default function ViewSecret() {
-  const { t } = useTranslation();
-  const { id } = useParams<{ id: string }>();
-  const [status, setStatus] = useState<Status>("loading");
-  const [plaintext, setPlaintext] = useState("");
-  const [error, setError] = useState("");
-  const [copied, setCopied] = useState(false);
- 
-  useEffect(() => {
-    async function fetchAndDecrypt() {
-      const keyStr = window.location.hash.slice(1);
-      if (!keyStr || !id) {
-        setError(t("view.invalidLink"));
-        setStatus("error");
-        return;
-      }
- 
-      try {
-        const result = await getSecret(id);
-        const key = await importKey(keyStr);
-        const decrypted = await decrypt(result.ciphertext, key, result.id);
-        setPlaintext(decrypted);
-        setStatus("revealed");
-      } catch (err) {
-        const msg = err instanceof Error ? err.message : "Failed to decrypt";
-        if (msg.includes("not found") || msg.includes("already viewed")) {
-          setStatus("not-found");
-        } else {
-          setError(msg);
-          setStatus("error");
-        }
-      }
-    }
- 
-    fetchAndDecrypt();
-  }, [id, t]);
- 
-  async function handleCopy() {
-    await navigator.clipboard.writeText(plaintext);
-    setCopied(true);
-    setTimeout(() => setCopied(false), 2000);
-  }
- 
-  return (
-    <div className="view-container">
-      {status === "loading" && (
-        <div className="card">
-          <div className="loading-card">
-            <div className="loading-spinner" />
-            <p>{t("view.loading")}</p>
-          </div>
-        </div>
-      )}
- 
-      {status === "revealed" && (
-        <div className="card">
-          <div className="revealed-card">
-            <div className="destroyed-banner">
-              <ShieldOff size={15} />
-              <span>{t("view.destroyed")}</span>
-            </div>
-            <div className="secret-content">{plaintext}</div>
-            <button
-              className={`btn btn-sm btn-full ${copied ? "btn-success" : "btn-secondary"}`}
-              onClick={handleCopy}
-              aria-label={t("view.copySecret")}
-            >
-              {copied ? <Check size={15} /> : <Copy size={15} />}
-              {copied ? t("view.copiedClipboard") : t("view.copySecret")}
-            </button>
-          </div>
-        </div>
-      )}
- 
-      {status === "not-found" && (
-        <div className="card">
-          <div className="not-found-card">
-            <div className="not-found-icon">
-              <EyeOff size={22} />
-            </div>
-            <h2>{t("view.notFoundTitle")}</h2>
-            <p>{t("view.notFoundMsg")}</p>
-          </div>
-        </div>
-      )}
- 
-      {status === "error" && (
-        <div className="card">
-          <div className="error-card">
-            <div className="error-icon">
-              <AlertCircle size={22} />
-            </div>
-            <h2>{t("view.errorTitle")}</h2>
-            <p>{error || t("view.errorMsg")}</p>
-          </div>
-        </div>
-      )}
- 
-      <a href="/" className="back-link">
-        <ArrowLeft size={15} />
-        {status === "revealed" ? t("view.newSecret") : t("view.backHome")}
-      </a>
-    </div>
-  );
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/pages/index.html b/ui/coverage/pages/index.html deleted file mode 100644 index 03e7480..0000000 --- a/ui/coverage/pages/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for pages - - - - - - - - - -
-
-

All files pages

-
- -
- 95.65% - Statements - 66/69 -
- - -
- 93.75% - Branches - 45/48 -
- - -
- 86.66% - Functions - 13/15 -
- - -
- 100% - Lines - 66/66 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
CreateSecret.tsx -
-
95%38/4090%18/2090%9/10100%38/38
ViewSecret.tsx -
-
96.55%28/2996.42%27/2880%4/5100%28/28
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/ui/coverage/prettify.css b/ui/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/ui/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/ui/coverage/prettify.js b/ui/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/ui/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/ui/coverage/sort-arrow-sprite.png b/ui/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed6831..0000000 Binary files a/ui/coverage/sort-arrow-sprite.png and /dev/null differ diff --git a/ui/coverage/sorter.js b/ui/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/ui/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); diff --git a/ui/package-lock.json b/ui/package-lock.json index 9325701..c4c2a06 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -11,6 +11,7 @@ "i18next": "^25.8.17", "i18next-browser-languagedetector": "^8.2.1", "lucide-react": "^0.577.0", + "posthog-js": "^1.360.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^16.5.6", @@ -1058,6 +1059,331 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz", + "integrity": "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz", + "integrity": "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz", + "integrity": "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", + "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@posthog/core": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.23.2.tgz", + "integrity": "sha512-zTDdda9NuSHrnwSOfFMxX/pyXiycF4jtU1kTr8DL61dHhV+7LF6XF1ndRZZTuaGGbfbb/GJYkEsjEX9SXfNZeQ==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + } + }, + "node_modules/@posthog/types": { + "version": "1.360.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.360.0.tgz", + "integrity": "sha512-roypbiJ49V3jWlV/lzhXGf0cKLLRj69L4H4ZHW6YsITHlnjQ12cgdPhPS88Bb9nW9xZTVSGWWDjfNGsdgAxsNg==", + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1590,6 +1916,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1610,6 +1945,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1955,6 +2297,31 @@ "url": "https://opencollective.com/express" } }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -2066,6 +2433,18 @@ "license": "MIT", "peer": true }, + "node_modules/dompurify": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.307", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", @@ -2183,6 +2562,12 @@ } } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2332,6 +2717,12 @@ "dev": true, "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -2445,6 +2836,12 @@ "node": ">=6" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2600,6 +2997,15 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2656,6 +3062,37 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/posthog-js": { + "version": "1.360.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.360.0.tgz", + "integrity": "sha512-jkyO+T97yi6RuiexOaXC7AnEGiC+yIfGU5DIUzI5rqBH6MltmtJw/ve2Oxc4jeua2WDr5sXMzo+SS+acbpueAA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", + "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@posthog/core": "1.23.2", + "@posthog/types": "1.360.0", + "core-js": "^3.38.1", + "dompurify": "^3.3.2", + "fflate": "^0.4.8", + "preact": "^10.28.2", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.1.0" + } + }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -2672,6 +3109,30 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2682,6 +3143,12 @@ "node": ">=6" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -2890,6 +3357,27 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3068,6 +3556,12 @@ "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -3283,6 +3777,12 @@ "node": ">=18" } }, + "node_modules/web-vitals": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.1.0.tgz", + "integrity": "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -3318,6 +3818,21 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/ui/package.json b/ui/package.json index bc46899..ce7ffe8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,6 +14,7 @@ "i18next": "^25.8.17", "i18next-browser-languagedetector": "^8.2.1", "lucide-react": "^0.577.0", + "posthog-js": "^1.360.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^16.5.6", diff --git a/ui/src/lib/crypto.ts b/ui/src/lib/crypto.ts index 006807e..95dab5d 100644 --- a/ui/src/lib/crypto.ts +++ b/ui/src/lib/crypto.ts @@ -47,7 +47,7 @@ export async function importKey(base64url: string): Promise { const raw = base64urlDecode(base64url); return crypto.subtle.importKey( "raw", - raw.buffer as ArrayBuffer, + new Uint8Array(raw) as unknown as ArrayBuffer, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"], diff --git a/ui/src/lib/posthog.ts b/ui/src/lib/posthog.ts new file mode 100644 index 0000000..dc4f805 --- /dev/null +++ b/ui/src/lib/posthog.ts @@ -0,0 +1,15 @@ +import posthog from "posthog-js"; + +const POSTHOG_KEY = import.meta.env.VITE_POSTHOG_KEY; +const POSTHOG_HOST = import.meta.env.VITE_POSTHOG_HOST || "https://us.i.posthog.com"; + +if (POSTHOG_KEY) { + posthog.init(POSTHOG_KEY, { + api_host: POSTHOG_HOST, + capture_pageview: true, + capture_pageleave: true, + autocapture: true, + }); +} + +export default posthog; diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 699a1f6..4b9e2fa 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -1,6 +1,8 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { PostHogProvider } from "posthog-js/react"; +import posthog from "./lib/posthog"; import Layout from "./components/Layout"; import CreateSecret from "./pages/CreateSecret"; import ViewSecret from "./pages/ViewSecret"; @@ -9,13 +11,15 @@ import "./index.css"; createRoot(document.getElementById("root")!).render( - - - - } /> - } /> - - - + + + + + } /> + } /> + + + + ); diff --git a/ui/src/pages/CreateSecret.tsx b/ui/src/pages/CreateSecret.tsx index ff8a3b2..8d2d856 100644 --- a/ui/src/pages/CreateSecret.tsx +++ b/ui/src/pages/CreateSecret.tsx @@ -13,6 +13,7 @@ import { } from "lucide-react"; import { generateKey, exportKey, encrypt } from "../lib/crypto"; import { createSecret } from "../lib/api"; +import posthog from "../lib/posthog"; const TTL_OPTIONS = [ { value: 1, label: "1h" }, @@ -48,8 +49,10 @@ export default function CreateSecret() { const keyStr = await exportKey(key); const pathId = result.alias ?? result.id; setLink(`${window.location.origin}/s/${pathId}?lng=${i18n.language}#${keyStr}`); + posthog.capture("secret_created", { ttl_hours: ttlHours }); setSecret(""); } catch (err) { + posthog.capture("secret_create_failed"); setError(err instanceof Error ? err.message : "Something went wrong"); } finally { setLoading(false); @@ -58,6 +61,7 @@ export default function CreateSecret() { async function handleCopy() { await navigator.clipboard.writeText(link); + posthog.capture("secret_link_copied"); setCopied(true); setTimeout(() => setCopied(false), 2000); } diff --git a/ui/src/pages/ViewSecret.tsx b/ui/src/pages/ViewSecret.tsx index a2b5c2f..2444286 100644 --- a/ui/src/pages/ViewSecret.tsx +++ b/ui/src/pages/ViewSecret.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import { importKey, decrypt } from "../lib/crypto"; import { getSecret } from "../lib/api"; +import posthog from "../lib/posthog"; type Status = "loading" | "revealed" | "not-found" | "error"; @@ -36,12 +37,15 @@ export default function ViewSecret() { const key = await importKey(keyStr); const decrypted = await decrypt(result.ciphertext, key, result.id); setPlaintext(decrypted); + posthog.capture("secret_viewed"); setStatus("revealed"); } catch (err) { const msg = err instanceof Error ? err.message : "Failed to decrypt"; if (msg.includes("not found") || msg.includes("already viewed")) { + posthog.capture("secret_not_found"); setStatus("not-found"); } else { + posthog.capture("secret_view_failed"); setError(msg); setStatus("error"); } diff --git a/ui/tsconfig.app.tsbuildinfo b/ui/tsconfig.app.tsbuildinfo deleted file mode 100644 index 9bc6474..0000000 --- a/ui/tsconfig.app.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"root":["./src/main.tsx","./src/components/languageselector.test.tsx","./src/components/languageselector.tsx","./src/components/layout.test.tsx","./src/components/layout.tsx","./src/components/securitymodal.test.tsx","./src/components/securitymodal.tsx","./src/i18n/index.test.ts","./src/i18n/index.ts","./src/lib/api.test.ts","./src/lib/api.ts","./src/lib/crypto.test.ts","./src/lib/crypto.ts","./src/pages/createsecret.test.tsx","./src/pages/createsecret.tsx","./src/pages/viewsecret.test.tsx","./src/pages/viewsecret.tsx","./src/test/render.tsx","./src/test/setup.ts"],"version":"5.9.3"} \ No newline at end of file