Commit Graph

64 Commits

Author SHA1 Message Date
677bbbb37e Fix: Correct method name from getOffersByService to getOffersForService
The storage interface defines getOffersForService() but RPC
handler was calling getOffersByService(), causing runtime error.
2025-12-12 21:11:56 +01:00
caae10bcac Fix: Pass offers to createService method
The createService storage method expects offers in the request,
but publishService wasn't passing them. This caused undefined
error when d1.ts tried to call request.offers.map().

Now correctly passes offers to createService which handles
creating both the service and all offers atomically.
2025-12-12 21:09:15 +01:00
34babd036e Fix: Auto-claim should not validate claim message format
Auto-claim was incorrectly using validateUsernameClaim() which
expects 'claim:{username}:{timestamp}' message format. This failed
when users tried to auto-claim via publishService or getService.

Now auto-claim only:
- Validates username format
- Verifies signature against the actual message
- Claims the username

This allows implicit username claiming on first authenticated request.
2025-12-12 21:03:44 +01:00
876ac2602c Fix: Correct validateUsernameClaim function calls
The function expects 4 separate parameters, not an object.
This was causing 'Username must be a string' errors because
the entire object was being passed as the username parameter.
2025-12-12 21:00:11 +01:00
df9f3311e9 Fix: Add missing continue statement in message validation
The message validation was missing a continue statement, causing
the handler to continue executing even after pushing an error response.
This led to undefined errors when trying to map over undefined values.
2025-12-12 20:52:24 +01:00
9f30f8b46d Implement implicit username claiming in RPC handler
Modified verifyAuth() to automatically claim usernames on first use.
When a username is not claimed and a publicKey is provided in the
RPC request, the server will validate and auto-claim it.

Changes:
- Added publicKey parameter to verifyAuth() function
- Added publicKey field to RpcRequest interface
- Updated RpcHandler type to include publicKey parameter
- Modified all method handlers to pass publicKey to verifyAuth()
- Updated handleRpc() to extract publicKey from requests

🤖 Generated with Claude Code
https://claude.com/claude-code

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 20:22:23 +01:00
17765a9f4f refactor: Convert to RPC interface with single /rpc endpoint
BREAKING CHANGES:
- Replaced REST API with RPC interface
- Single POST /rpc endpoint for all operations
- Removed auth middleware (per-method auth instead)
- Support for batch operations
- Message format changed for all methods

Changes:
- Created src/rpc.ts with all method handlers
- Simplified src/app.ts to only handle /rpc endpoint
- Removed src/middleware/auth.ts
- Updated README.md with complete RPC documentation
2025-12-12 19:51:58 +01:00
4e73157a16 migration: Convert peer_id to username in offers and ice_candidates
This migration aligns the D1 database schema with the unified Ed25519
authentication system that replaced the dual peerId/secret system.

Changes:
- Renames peer_id to username in offers table
- Renames answerer_peer_id to answerer_username in offers table
- Renames peer_id to username in ice_candidates table
- Adds service_fqn column to offers table
- Updates all indexes and foreign keys
2025-12-12 19:20:54 +01:00
8d47424a82 fix: Remove authSecret reference from worker config
The authSecret variable was removed but still referenced in the config
object, causing the worker to crash on all requests.
2025-12-12 19:18:24 +01:00
1612bd78b7 refactor: Unify polling endpoint and remove AUTH_SECRET
BREAKING CHANGES:
- Renamed /offers/poll to /poll (generic polling endpoint)
- Removed /offers/answered endpoint (use /poll instead)
- Removed AUTH_SECRET environment variable (Ed25519 auth only)
- Updated auth message format from 'pollOffers' to 'poll'
2025-12-12 19:13:11 +01:00
01b751afc3 docs: Fix DELETE endpoint auth and anonymous users description
- DELETE /services/:fqn uses request body for auth, not query parameters
- Updated anonymous users description to reflect server capabilities
  (not client auto-claiming behavior which was removed)
2025-12-12 17:44:35 +01:00
0a98ace6f7 docs: Update README for unified Ed25519 authentication
- Remove POST /register endpoint documentation
- Update all endpoints to show signature-based auth (username, signature, message)
- Remove Authorization header examples (replaced with body/query params)
- Add anonymous username documentation (anon-{timestamp}-{random})
- Update database schema to show username-based tables
- Remove AUTH_SECRET from configuration
- Update security section with Ed25519 authentication details
2025-12-10 22:19:06 +01:00
51fe405440 Unified Ed25519 authentication - remove peer_id/credentials system
BREAKING CHANGE: Remove dual authentication system

- Remove POST /register endpoint - no longer needed
- Remove peer_id/secret credential-based auth
- All authentication now uses username + Ed25519 signatures
- Anonymous users can generate random usernames (anon-{timestamp}-{hex})

Database schema:
- Rename peer_id → username in offers table
- Rename answerer_peer_id → answerer_username in offers table
- Rename peer_id → username in ice_candidates table
- Remove secret column from offers table
- Add FK constraints for username columns

Storage layer:
- Update D1 and SQLite implementations
- All methods use username instead of peerId
- Remove secret-related code

Auth middleware:
- Replace validateCredentials() with Ed25519 signature verification
- Extract auth from request body (POST) or query params (GET)
- Verify signature against username's public key
- Validate message format and timestamp

Crypto utilities:
- Remove generatePeerId(), encryptPeerId(), decryptPeerId(), validateCredentials()
- Add generateAnonymousUsername() - creates anon-{timestamp}-{random}
- Add validateAuthMessage() - validates auth message format

Config:
- Remove authSecret from Config interface (no longer needed)

All server endpoints updated to use getAuthenticatedUsername()
2025-12-10 22:06:45 +01:00
95596dd462 Update README to document current v0.4 API
- Remove outdated UUID-based endpoint documentation
- Document actual service:version@username FQN format
- Add /offers/poll combined polling endpoint
- Update all endpoint paths to match actual implementation
- Document ICE candidate role filtering
- Add migration notes from v0.3.x
2025-12-10 21:03:51 +01:00
1bf21d7df8 Include both offerer and answerer ICE candidates in polling endpoint
- Add role and peerId to ICE candidate responses for matching
- Offerers can now see their own candidates (for debugging/sync)
- Answerers can poll same endpoint to get offerer candidates
- Each candidate tagged with role ('offerer' or 'answerer') and peerId
- Enables proper bidirectional ICE candidate exchange
2025-12-10 19:51:31 +01:00
e3ede0033e Fix UNIQUE constraint: Use (service_name, version, username) instead of service_fqn
- Change UNIQUE constraint to composite key on separate columns
- Move upsert logic into D1Storage.createService() for atomic operation
- Delete existing service and its offers before inserting new one
- Remove redundant delete logic from app.ts endpoint
- Fixes 'UNIQUE constraint failed: services.service_fqn' error when republishing
2025-12-10 19:42:03 +01:00
cfa58f1dfa Add combined polling endpoint for answers and ICE candidates
- Add GET /offers/poll endpoint for efficient batch polling
- Returns both answered offers and ICE candidates in single request
- Supports timestamp-based filtering with 'since' parameter
- Reduces HTTP overhead from 2N requests to 1 request
- Filters ICE candidates by role (answerer candidates for offerer)
2025-12-10 19:32:52 +01:00
c14a8c24fc Add efficient batch polling endpoint for answered offers
Added GET /offers/answered endpoint that returns all answered offers
for the authenticated peer with optional 'since' timestamp filtering.

This allows offerers to efficiently poll for all incoming connections
in a single request instead of polling each offer individually.
2025-12-10 19:17:19 +01:00
b282bf6470 Fix D1 storage: Insert service_id when creating offers
The createOffers function was not inserting the service_id column even
though it was passed in the CreateOfferRequest. This caused all offers
to have NULL service_id, making getOffersForService return empty results.

Fixed:
- Added service_id to INSERT statement in createOffers
- Added serviceId to created offer objects
- Added serviceId to rowToOffer mapping

This resolves the 'No available offers' error when trying to connect
to a published service.
2025-12-10 18:52:11 +01:00
9088abe305 Fix fresh schema to match D1 storage expectations
Changed offers table to use service_id (nullable) instead of service_fqn.
This matches the actual D1 storage implementation in d1.ts which expects:
- service_id TEXT (optional link to service)
- NOT service_fqn (that's only in the services table)

Resolves 'NOT NULL constraint failed: offers.service_fqn' error.
2025-12-10 18:32:43 +01:00
00c5bbc501 Update database configuration and add fresh schema
- Update wrangler.toml with new D1 database ID
- Add fresh_schema.sql for clean database initialization
- Applied schema to fresh D1 database
- Server redeployed with correct database binding

This resolves the 'table services has no column named service_name' error
by ensuring the database has the correct v0.4.1+ schema.
2025-12-10 18:17:53 +01:00
85a3de65e2 Fix signature validation bug for serviceFqn with colons
The validateServicePublish function was incorrectly parsing the signature
message when serviceFqn contained colons (e.g., 'chat:2.0.0@user').

Old logic: Split by ':' and expected exactly 4 parts
Problem: serviceFqn 'chat:2.0.0@user' contains a colon, so we get 5 parts

Fixed:
- Allow parts.length >= 4
- Extract timestamp from the last part
- Reconstruct serviceFqn from all middle parts (parts[2] to parts[length-2])

This fixes the '403 Invalid signature for username' error that was
preventing service publication.
2025-12-09 22:59:02 +01:00
8111cb9cec v0.5.0: Service discovery and FQN format refactoring
- Changed service FQN format: service:version@username (colon instead of @)
- Added service discovery: direct lookup, random selection, paginated queries
- Updated parseServiceFqn to handle optional username for discovery
- Removed UUID privacy layer (service_index table)
- Updated storage interface with discovery methods (discoverServices, getRandomService, getServiceByFqn)
- Removed deprecated methods (getServiceByUuid, queryService, listServicesForUsername, findServicesByName, touchUsername, batchCreateServices)
- Updated API routes: /services/:fqn with three modes (direct, random, paginated)
- Changed offer/answer/ICE routes to offer-specific: /services/:fqn/offers/:offerId/*
- Added extracted fields to services table (service_name, version, username) for efficient discovery
- Created migration 0007 to update schema and migrate existing data
- Added discovery indexes for performance

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 22:22:37 +01:00
b446adaee4 fix: better error handling for public key constraint
- Add try/catch in claimUsername to handle UNIQUE constraint
- Return meaningful error: 'This public key has already claimed a different username'
- Enable observability logs for better debugging
2025-12-08 21:31:36 +01:00
163e1f73d4 fix: update D1 schema to match v0.4.0 service-to-offers relationship
- Add service_id column to offers table
- Remove offer_id column from services table
- Add index for service_id in offers
2025-12-07 22:31:34 +01:00
1d47d47ef7 feat: add database migration for service-to-offers refactor
- Add service_id column to offers table
- Remove offer_id column from services table
- Update VERSION to 0.4.0 in wrangler.toml
2025-12-07 22:28:14 +01:00
1d70cd79e8 feat: refactor to service-based WebRTC signaling endpoints
BREAKING CHANGE: Replace offer-based endpoints with service-based signaling

- Add POST /services/:uuid/answer
- Add GET /services/:uuid/answer
- Add POST /services/:uuid/ice-candidates
- Add GET /services/:uuid/ice-candidates
- Remove all /offers/* endpoints (POST /offers, GET /offers/mine, etc.)
- Server auto-detects peer's offer when offerId is omitted
- Update README with new service-based API documentation
- Bump version to 0.4.0

This change simplifies the API by focusing on services rather than individual offers.
WebRTC signaling (answer/ICE) now operates at the service level, with automatic
offer detection when needed.
2025-12-07 22:17:24 +01:00
2aa1fee4d6 docs: update server README to remove outdated sections
- Remove obsolete POST /index/:username/query endpoint
- Remove non-existent PUT /offers/:offerId/heartbeat endpoint
- Update architecture diagram to reflect semver discovery
- Update database schema to show service-to-offers relationship
2025-12-07 22:07:16 +01:00
d564e2250f docs: Update README with semver matching and offers array 2025-12-07 22:00:40 +01:00
06ec5020f7 0.3.0 2025-12-07 21:59:15 +01:00
5c71f66a26 feat: Add semver-compatible service discovery with privacy
## Breaking Changes

### Removed Endpoints
- Removed GET /users/:username/services (service listing)
- Services are now completely hidden - cannot be enumerated

### Updated Endpoints
- GET /users/:username/services/:fqn now supports semver matching
- Requesting chat@1.0.0 will match chat@1.2.3, chat@1.5.0, etc.
- Will NOT match chat@2.0.0 (different major version)

## New Features

### Semantic Versioning Support
- Compatible version matching following semver rules (^1.0.0)
- Major version must match exactly
- For major version 0, minor must also match (0.x.y is unstable)
- Available version must be >= requested version
- Prerelease versions require exact match

### Privacy Improvements
- All services are now hidden by default
- No way to enumerate or list services for a username
- Must know exact service name to discover

## Implementation

### Server (src/)
- crypto.ts: Added parseVersion(), isVersionCompatible(), parseServiceFqn()
- storage/types.ts: Added findServicesByName() interface method
- storage/sqlite.ts: Implemented findServicesByName() with LIKE query
- storage/d1.ts: Implemented findServicesByName() with LIKE query
- app.ts: Updated GET /:username/services/:fqn with semver matching

### Semver Matching Logic
- Parse requested version: chat@1.0.0 → {name: "chat", version: "1.0.0"}
- Find all services with matching name: chat@*
- Filter to compatible versions using semver rules
- Return first match (most recently created)

## Examples

Request: chat@1.0.0
Matches: chat@1.0.0, chat@1.2.3, chat@1.9.5
Does NOT match: chat@0.9.0, chat@2.0.0, chat@1.0.0-beta

🤖 Generated with Claude Code
2025-12-07 21:56:19 +01:00
ca3db47009 Refactor: Consolidate service/offer architecture
## Breaking Changes

### Server
- Services can now have multiple offers instead of single offer
- POST /users/:username/services accepts `offers` array instead of `sdp`
- GET /users/:username/services/:fqn returns `offers` array in response
- GET /services/:uuid returns `offers` array in response
- Database schema: removed `offer_id` from services table, added `service_id` to offers table
- Added `batchCreateServices()` and `getOffersForService()` methods

### Client
- `PublishServiceOptions` interface: `offers` array instead of `sdp` string
- `Service` interface: `offers` array instead of `offerId` and `sdp`
- `ServiceRequest` interface: `offers` array instead of `sdp`
- RondevuSignaler.setOffer() sends offers array to server
- Updated to extract offerId from first offer in service response

## New Features
- Support for multiple simultaneous offers per service (connection pooling)
- Batch service creation endpoint for reduced server load
- Proper one-to-many relationship between services and offers

## Implementation Details

### Server Changes (src/storage/)
- sqlite.ts: Added service_id column to offers, removed offer_id from services
- d1.ts: Updated to match new interface
- types.ts: Updated interfaces for Service, Offer, CreateServiceRequest
- app.ts: Updated all service endpoints to handle offers array

### Client Changes (src/)
- api.ts: Added OfferRequest and ServiceOffer interfaces
- rondevu-service.ts: Updated PublishServiceOptions to use offers array
- rondevu-signaler.ts: Updated to send/receive offers array

## Migration Notes
- No backwards compatibility - this is a breaking change
- Services published with old API will not work with new server
- Clients must update to new API to work with updated server

🤖 Generated with Claude Code
2025-12-07 21:49:23 +01:00
3efed6e9d2 Fix service reconnection: return available offer from pool
Modified /services/:uuid endpoint to return an available (unanswered)
offer from the service's offer pool instead of always returning the
initial offer. This fixes reconnection failures where clients would
try to answer already-consumed offers.

Changes:
- Query all offers from the service's peer ID
- Return first unanswered offer
- Return 503 if no offers available

Fixes: "Offer already answered" errors on reconnection attempts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 13:47:00 +01:00
1257867dff fix: implement upsert behavior for service creation
When a service is republished (e.g., for TTL refresh), the old service
is now deleted before creating a new one, preventing UNIQUE constraint
errors on (username, service_fqn).

Changes:
- Query for existing service before creation
- Delete existing service if found
- Create new service with same username/serviceFqn

This enables the client's TTL auto-refresh feature to work correctly.
2025-12-06 13:04:45 +01:00
52cf734858 Remove legacy V1 code and clean up unused remnants
- Delete unused bloom.ts module (leftover from topic-based discovery)
- Remove maxTopicsPerOffer configuration (no longer used)
- Remove unused info field from Offer types
- Simplify generateOfferHash() to only hash SDP (remove topics param)
- Update outdated comments referencing deprecated features
- Remove backward compatibility topics field from answer responses

This completes the migration to V2 service-based architecture by
removing all remnants of the V1 topic-based system.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 12:06:02 +01:00
5622867411 Add upsert behavior to service creation
- Delete existing service before creating new one
- Prevents UNIQUE constraint error on (username, service_fqn)
- Enables seamless service republishing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 11:46:21 +01:00
ac0e064e34 Fix answer response field names for V2 API compatibility
- Change 'answererPeerId' to 'answererId'
- Change 'answerSdp' to 'sdp'
- Add 'topics' field (empty array) for client compatibility

This ensures the server response matches the expected format
in the client's AnsweredOffer interface.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-06 11:37:31 +01:00
e7cd90b905 Fix error handling scope issue in service creation
The error handler was referencing variables (username, serviceFqn, offers)
that were declared inside the try block. If an error occurred before these
were defined, the error handler itself would fail, resulting in non-JSON
responses that caused "JSON.parse: unexpected character" errors on the client.

Fixed by:
- Declaring variables at function scope
- Initializing offers as empty array
- Using destructuring assignment for username/serviceFqn

This ensures the error handler can always access these variables safely,
even if an early error occurs, and will always return proper JSON responses.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:56:06 +01:00
67b1decbad debug: add detailed error logging to service creation endpoint
Return error details in response to help debug internal server errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:37:57 +01:00
e9d0f26726 fix: add validateServicePublish for correct signature verification
The service publishing endpoint was using validateUsernameClaim which
expects the message format "claim:{username}:{timestamp}", but clients
send "publish:{username}:{serviceFqn}:{timestamp}".

Added validateServicePublish function to properly validate service
publishing signatures with the correct message format.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:31:42 +01:00
595eac8692 feat: add V2 database migration for D1
Add migration to create V2 tables:
- offers (with ICE candidates)
- usernames (with Ed25519 public keys)
- services (with service discovery)
- service_index (privacy layer)

Applied to production D1 database: rondevu-offers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:24:29 +01:00
65a13fefa4 fix: use async ed25519.verifyAsync function
Switch from sync verify() to async verifyAsync() to work with
hashes.sha512Async which uses WebCrypto API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:19:47 +01:00
1dadf5461e fix: use Web Crypto API for Cloudflare Workers compatibility
- d1.ts: Use global crypto.randomUUID() instead of importing from 'crypto'
- sqlite.ts: Use 'node:crypto' import for Node.js compatibility

This fixes the Cloudflare Workers deployment error:
"The package 'crypto' wasn't found on the file system but is built into node"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 19:05:23 +01:00
bd35f7919c chore: bump version to 0.2.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:46:00 +01:00
683bc42bf0 fix: initialize SHA-512 hash function for @noble/ed25519 v3
@noble/ed25519 v3.0.0 requires explicit SHA-512 hash function setup
before using any cryptographic operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:45:04 +01:00
c3fc498c81 fix: correct server version to 0.2.0 (minor bump from 0.1.4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:30:29 +01:00
4f772c50c9 feat: add V2 service publishing and username claiming APIs
- Add POST /services endpoint for publishing services with username verification
- Add DELETE /services/:serviceId endpoint for unpublishing services
- Add GET /services/:serviceFqn endpoint for service discovery
- Add POST /usernames/claim endpoint with Ed25519 signature verification
- Add POST /usernames/renew endpoint for extending username TTL
- Add GET /usernames/:username endpoint for checking username availability
- Add username expiry tracking and cleanup (365-day default TTL)
- Add service-to-offer relationship tracking
- Add signature verification for username operations
- Update storage schema for usernames and services tables
- Add comprehensive README documentation for V2 APIs
- Update version to 0.8.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:27:12 +01:00
08e1433088 Update README: Remove custom peer ID documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:23:09 +01:00
70d018c666 Remove custom peer ID feature for security
Always generate cryptographically random 128-bit peer IDs to prevent peer ID hijacking vulnerability. This ensures peer IDs are secure through collision resistance rather than relying on expiration-based protection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:19:16 +01:00
2cff4c8544 0.1.4 v0.1.4 2025-11-22 17:32:56 +01:00