Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions app/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { defineConfig, devices } from '@playwright/test';
import { defineConfig, devices } from "@playwright/test";

/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './e2e',
testDir: "./e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
Expand All @@ -14,31 +14,31 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:3000',
baseURL: "http://127.0.0.1:3000",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: "on-first-retry",
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
name: "webkit",
use: { ...devices["Desktop Safari"] },
},

/* Test against mobile viewports. */
Expand All @@ -64,8 +64,8 @@ export default defineConfig({

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: 'http://127.0.0.1:3000',
command: "yarn dev",
url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI,
},
});
});
25 changes: 13 additions & 12 deletions app/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { render } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import { Provider } from './provider'
import { render } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { BrowserRouter } from "react-router-dom";

describe('App', () => {
it('renders without crashing', () => {
import App from "./App";
import { Provider } from "./provider";

describe("App", () => {
it("renders without crashing", () => {
render(
<BrowserRouter>
<Provider>
<App />
</Provider>
</BrowserRouter>
)
</BrowserRouter>,
);
// Just verify the app renders without throwing errors
expect(document.querySelector('body')).toBeInTheDocument()
expect(document.querySelector("body")).toBeInTheDocument();
// Debug what's actually rendered
// console.log(screen.debug())
})
})
});
});
14 changes: 8 additions & 6 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { Route, Routes } from "react-router-dom";
import { ErrorBoundary } from "@/components/error-boundary";

import { ErrorBoundary } from "@/components/error-boundary";
import IndexPage from "@/pages/index";
import DocsPage from "@/pages/docs";
import PricingPage from "@/pages/pricing";
Expand All @@ -16,11 +16,13 @@ function App() {

useEffect(() => {
// Initialize device on app startup
initializeDevice().then((device) => {
setCurrentDevice(device);
}).catch((error) => {
console.error('Failed to initialize device:', error);
});
initializeDevice()
.then((device) => {
setCurrentDevice(device);
})
.catch((error) => {
console.error("Failed to initialize device:", error);
});
}, [setCurrentDevice]);

return (
Expand Down
46 changes: 31 additions & 15 deletions app/src/components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Button } from '@heroui/button';
import { Card, CardBody, CardHeader } from '@heroui/card';
import React from "react";
import { Button } from "@heroui/button";
import { Card, CardBody, CardHeader } from "@heroui/card";

interface ErrorBoundaryState {
hasError: boolean;
Expand All @@ -13,7 +13,10 @@ interface ErrorBoundaryProps {
fallback?: React.ComponentType<{ error?: Error; resetError: () => void }>;
}

export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
export class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
Expand All @@ -24,7 +27,7 @@ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoun
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
console.error("ErrorBoundary caught an error:", error, errorInfo);
this.setState({ error, errorInfo });
}

Expand All @@ -36,10 +39,21 @@ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoun
if (this.state.hasError) {
if (this.props.fallback) {
const FallbackComponent = this.props.fallback;
return <FallbackComponent error={this.state.error} resetError={this.resetError} />;

return (
<FallbackComponent
error={this.state.error}
resetError={this.resetError}
/>
);
}

return <DefaultErrorFallback error={this.state.error} resetError={this.resetError} />;
return (
<DefaultErrorFallback
error={this.state.error}
resetError={this.resetError}
/>
);
}

return this.props.children;
Expand All @@ -57,7 +71,9 @@ function DefaultErrorFallback({ error, resetError }: ErrorFallbackProps) {
<Card className="w-full max-w-md">
<CardHeader className="pb-0">
<div className="flex flex-col">
<h1 className="text-lg font-semibold text-danger">Something went wrong</h1>
<h1 className="text-lg font-semibold text-danger">
Something went wrong
</h1>
<p className="text-small text-default-500">
An unexpected error occurred in the application
</p>
Expand All @@ -72,19 +88,19 @@ function DefaultErrorFallback({ error, resetError }: ErrorFallbackProps) {
</div>
)}
<div className="flex gap-2">
<Button
color="primary"
<Button
className="flex-1"
color="primary"
variant="solid"
onPress={resetError}
className="flex-1"
>
Try Again
</Button>
<Button
color="default"
<Button
className="flex-1"
color="default"
variant="bordered"
onPress={() => window.location.reload()}
className="flex-1"
>
Reload Page
</Button>
Expand All @@ -104,4 +120,4 @@ export function useErrorHandler() {
// This will trigger the nearest error boundary
throw error;
};
}
}
64 changes: 32 additions & 32 deletions app/src/components/pairing/qr-display.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { useEffect, useState } from 'react';
import { Card, CardBody } from '@heroui/card';
import { Button } from '@heroui/button';
import { Spinner } from '@heroui/spinner';
import { generateQRCodeDataURL, generatePairingData } from '../../crypto/qr';
import { generateDeviceFingerprint, formatSafetyWords } from '../../crypto/fingerprint';
import type { Device } from '../../state/types';
import type { Device } from "../../state/types";

import { useEffect, useState } from "react";
import { Card, CardBody } from "@heroui/card";
import { Button } from "@heroui/button";
import { Spinner } from "@heroui/spinner";

import { generateQRCodeDataURL, generatePairingData } from "../../crypto/qr";
import {
generateDeviceFingerprint,
formatSafetyWords,
} from "../../crypto/fingerprint";

interface QRDisplayProps {
device: Device;
Expand All @@ -13,10 +18,10 @@ interface QRDisplayProps {
}

export function QRDisplay({ device, onClose, className }: QRDisplayProps) {
const [qrCodeUrl, setQrCodeUrl] = useState<string>('');
const [safetyWords, setSafetyWords] = useState<string>('');
const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
const [safetyWords, setSafetyWords] = useState<string>("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>('');
const [error, setError] = useState<string>("");

useEffect(() => {
generateQRData();
Expand All @@ -25,22 +30,29 @@ export function QRDisplay({ device, onClose, className }: QRDisplayProps) {
const generateQRData = async () => {
try {
setLoading(true);
setError('');
setError("");

// Generate pairing data (ICE servers now use defaults, not embedded in QR)
const signalingURL = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws/signaling`;
const signalingURL = `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/ws/signaling`;

const pairingData = generatePairingData(device, signalingURL);

// Generate QR code
const qrUrl = await generateQRCodeDataURL(pairingData);

setQrCodeUrl(qrUrl);

// Generate safety words for verification
const fingerprint = await generateDeviceFingerprint(device.id, device.pubKeyJwk);
const fingerprint = await generateDeviceFingerprint(
device.id,
device.pubKeyJwk,
);

setSafetyWords(formatSafetyWords(fingerprint.safetyWords));
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate QR code');
setError(
err instanceof Error ? err.message : "Failed to generate QR code",
);
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -94,11 +106,7 @@ export function QRDisplay({ device, onClose, className }: QRDisplayProps) {

{/* QR Code */}
<div className="bg-white p-4 rounded-lg shadow-sm border mb-6">
<img
src={qrCodeUrl}
alt="Pairing QR Code"
className="w-64 h-64"
/>
<img alt="Pairing QR Code" className="w-64 h-64" src={qrCodeUrl} />
</div>

{/* Device Info */}
Expand All @@ -124,18 +132,10 @@ export function QRDisplay({ device, onClose, className }: QRDisplayProps) {

{/* Actions */}
<div className="flex gap-2 w-full">
<Button
variant="light"
onPress={handleRefresh}
className="flex-1"
>
<Button className="flex-1" variant="light" onPress={handleRefresh}>
Refresh
</Button>
<Button
color="primary"
onPress={onClose}
className="flex-1"
>
<Button className="flex-1" color="primary" onPress={onClose}>
Done
</Button>
</div>
Expand All @@ -149,4 +149,4 @@ export function QRDisplay({ device, onClose, className }: QRDisplayProps) {
</CardBody>
</Card>
);
}
}
Loading
Loading