mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-12 20:03:24 +00:00
Unified Ed25519 authentication - remove credentials system
BREAKING CHANGE: Remove credential-based authentication
- Remove Credentials interface and all credential-related code
- Remove register() method from RondevuAPI
- Remove setCredentials() and getAuthHeader() methods
RondevuAPI changes:
- Constructor now requires username and keypair (not credentials)
- Add generateAuthParams() helper for automatic signature generation
- All API methods now include {username, signature, message} auth
- POST requests: auth in body
- GET requests: auth in query params
- Remove Authorization header from all fetch calls
Rondevu class changes:
- Make username optional in RondevuOptions (auto-generates anon username)
- Make keypair optional (auto-generates if not provided)
- Add generateAnonymousUsername() method (anon-{timestamp}-{random})
- Update initialize() to create API with username+keypair (no register call)
- Auto-claim username for anonymous users during initialize()
- Add lazy getAPI() to ensure initialization
Message format for auth:
- Format: action:username:params:timestamp
- Examples: publishService:alice:chat:1.0.0@alice:1234567890
- Each request generates unique signature with timestamp
Index exports:
- Remove Credentials export (no longer exists)
This commit is contained in:
174
src/api.ts
174
src/api.ts
@@ -9,11 +9,6 @@ ed25519.hashes.sha512Async = async (message: Uint8Array) => {
|
||||
return new Uint8Array(await crypto.subtle.digest('SHA-512', message as BufferSource))
|
||||
}
|
||||
|
||||
export interface Credentials {
|
||||
peerId: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
export interface Keypair {
|
||||
publicKey: string
|
||||
privateKey: string
|
||||
@@ -92,26 +87,26 @@ function base64ToBytes(base64: string): Uint8Array {
|
||||
export class RondevuAPI {
|
||||
constructor(
|
||||
private baseUrl: string,
|
||||
private credentials?: Credentials
|
||||
private username: string,
|
||||
private keypair: Keypair
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set credentials for authentication
|
||||
* Generate authentication parameters (username, signature, message) for API calls
|
||||
*/
|
||||
setCredentials(credentials: Credentials): void {
|
||||
this.credentials = credentials
|
||||
}
|
||||
private async generateAuthParams(action: string, params: string = ''): Promise<{
|
||||
username: string;
|
||||
signature: string;
|
||||
message: string;
|
||||
}> {
|
||||
const timestamp = Date.now();
|
||||
const message = params
|
||||
? `${action}:${this.username}:${params}:${timestamp}`
|
||||
: `${action}:${this.username}:${timestamp}`;
|
||||
|
||||
/**
|
||||
* Authentication header
|
||||
*/
|
||||
private getAuthHeader(): Record<string, string> {
|
||||
if (!this.credentials) {
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${this.credentials.peerId}:${this.credentials.secret}`,
|
||||
}
|
||||
const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey);
|
||||
|
||||
return { username: this.username, signature, message };
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -159,27 +154,6 @@ export class RondevuAPI {
|
||||
return await ed25519.verifyAsync(signature, messageBytes, publicKey)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Authentication
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Register a new peer and get credentials
|
||||
*/
|
||||
async register(): Promise<Credentials> {
|
||||
const response = await fetch(`${this.baseUrl}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
throw new Error(`Registration failed: ${error.error || response.statusText}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Offers
|
||||
// ============================================
|
||||
@@ -188,13 +162,14 @@ export class RondevuAPI {
|
||||
* Create one or more offers
|
||||
*/
|
||||
async createOffers(offers: OfferRequest[]): Promise<Offer[]> {
|
||||
const auth = await this.generateAuthParams('createOffers');
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/offers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeader(),
|
||||
},
|
||||
body: JSON.stringify({ offers }),
|
||||
body: JSON.stringify({ offers, ...auth }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -209,9 +184,13 @@ export class RondevuAPI {
|
||||
* Get offer by ID
|
||||
*/
|
||||
async getOffer(offerId: string): Promise<Offer> {
|
||||
const response = await fetch(`${this.baseUrl}/offers/${offerId}`, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const auth = await this.generateAuthParams('getOffer', offerId);
|
||||
const url = new URL(`${this.baseUrl}/offers/${offerId}`);
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -225,13 +204,14 @@ export class RondevuAPI {
|
||||
* Answer a specific offer from a service
|
||||
*/
|
||||
async postOfferAnswer(serviceFqn: string, offerId: string, sdp: string): Promise<{ success: boolean; offerId: string }> {
|
||||
const auth = await this.generateAuthParams('answerOffer', `${serviceFqn}:${offerId}`);
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/answer`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeader(),
|
||||
},
|
||||
body: JSON.stringify({ sdp }),
|
||||
body: JSON.stringify({ sdp, ...auth }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -254,13 +234,17 @@ export class RondevuAPI {
|
||||
answeredAt: number;
|
||||
}>;
|
||||
}> {
|
||||
const url = since
|
||||
? `${this.baseUrl}/offers/answered?since=${since}`
|
||||
: `${this.baseUrl}/offers/answered`;
|
||||
const auth = await this.generateAuthParams('getAnsweredOffers', since?.toString() || '');
|
||||
const url = new URL(`${this.baseUrl}/offers/answered`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
if (since) {
|
||||
url.searchParams.set('since', since.toString());
|
||||
}
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -289,13 +273,17 @@ export class RondevuAPI {
|
||||
createdAt: number;
|
||||
}>>;
|
||||
}> {
|
||||
const url = since
|
||||
? `${this.baseUrl}/offers/poll?since=${since}`
|
||||
: `${this.baseUrl}/offers/poll`;
|
||||
const auth = await this.generateAuthParams('pollOffers', since?.toString() || '');
|
||||
const url = new URL(`${this.baseUrl}/offers/poll`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
if (since) {
|
||||
url.searchParams.set('since', since.toString());
|
||||
}
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -309,9 +297,13 @@ export class RondevuAPI {
|
||||
* Get answer for a specific offer (offerer polls this)
|
||||
*/
|
||||
async getOfferAnswer(serviceFqn: string, offerId: string): Promise<{ sdp: string; offerId: string; answererId: string; answeredAt: number } | null> {
|
||||
const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/answer`, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const auth = await this.generateAuthParams('getOfferAnswer', `${serviceFqn}:${offerId}`);
|
||||
const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/answer`);
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
// 404 means not yet answered
|
||||
@@ -329,9 +321,14 @@ export class RondevuAPI {
|
||||
* Search offers by topic
|
||||
*/
|
||||
async searchOffers(topic: string): Promise<Offer[]> {
|
||||
const response = await fetch(`${this.baseUrl}/offers?topic=${encodeURIComponent(topic)}`, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const auth = await this.generateAuthParams('searchOffers', topic);
|
||||
const url = new URL(`${this.baseUrl}/offers`);
|
||||
url.searchParams.set('topic', topic);
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -349,13 +346,14 @@ export class RondevuAPI {
|
||||
* Add ICE candidates to a specific offer
|
||||
*/
|
||||
async addOfferIceCandidates(serviceFqn: string, offerId: string, candidates: RTCIceCandidateInit[]): Promise<{ count: number; offerId: string }> {
|
||||
const auth = await this.generateAuthParams('addIceCandidates', `${serviceFqn}:${offerId}`);
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/ice-candidates`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeader(),
|
||||
},
|
||||
body: JSON.stringify({ candidates }),
|
||||
body: JSON.stringify({ candidates, ...auth }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -370,10 +368,14 @@ export class RondevuAPI {
|
||||
* Get ICE candidates for a specific offer (with polling support)
|
||||
*/
|
||||
async getOfferIceCandidates(serviceFqn: string, offerId: string, since: number = 0): Promise<{ candidates: IceCandidate[]; offerId: string }> {
|
||||
const auth = await this.generateAuthParams('getIceCandidates', `${serviceFqn}:${offerId}:${since}`);
|
||||
const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/ice-candidates`)
|
||||
url.searchParams.set('since', since.toString())
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString(), { headers: this.getAuthHeader() })
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -396,13 +398,14 @@ export class RondevuAPI {
|
||||
* Service FQN must include username: service:version@username
|
||||
*/
|
||||
async publishService(service: ServiceRequest): Promise<Service> {
|
||||
const auth = await this.generateAuthParams('publishService', service.serviceFqn);
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/services`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeader(),
|
||||
},
|
||||
body: JSON.stringify(service),
|
||||
body: JSON.stringify({ ...service, username: auth.username }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -418,9 +421,13 @@ export class RondevuAPI {
|
||||
* Example: chat:1.0.0@alice
|
||||
*/
|
||||
async getService(serviceFqn: string): Promise<{ serviceId: string; username: string; serviceFqn: string; offerId: string; sdp: string; createdAt: number; expiresAt: number }> {
|
||||
const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}`, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const auth = await this.generateAuthParams('getService', serviceFqn);
|
||||
const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}`);
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -435,9 +442,13 @@ export class RondevuAPI {
|
||||
* Example: chat:1.0.0 (without @username)
|
||||
*/
|
||||
async discoverService(serviceVersion: string): Promise<{ serviceId: string; username: string; serviceFqn: string; offerId: string; sdp: string; createdAt: number; expiresAt: number }> {
|
||||
const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceVersion)}`, {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const auth = await this.generateAuthParams('discoverService', serviceVersion);
|
||||
const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceVersion)}`);
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -452,13 +463,15 @@ export class RondevuAPI {
|
||||
* Example: chat:1.0.0 (without @username)
|
||||
*/
|
||||
async discoverServices(serviceVersion: string, limit: number = 10, offset: number = 0): Promise<{ services: Array<{ serviceId: string; username: string; serviceFqn: string; offerId: string; sdp: string; createdAt: number; expiresAt: number }>; count: number; limit: number; offset: number }> {
|
||||
const auth = await this.generateAuthParams('discoverServices', `${serviceVersion}:${limit}:${offset}`);
|
||||
const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceVersion)}`)
|
||||
url.searchParams.set('limit', limit.toString())
|
||||
url.searchParams.set('offset', offset.toString())
|
||||
url.searchParams.set('username', auth.username);
|
||||
url.searchParams.set('signature', auth.signature);
|
||||
url.searchParams.set('message', auth.message);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: this.getAuthHeader(),
|
||||
})
|
||||
const response = await fetch(url.toString())
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
@@ -502,7 +515,6 @@ export class RondevuAPI {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeader(),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
publicKey,
|
||||
|
||||
@@ -14,7 +14,6 @@ export type {
|
||||
} from './types.js'
|
||||
|
||||
export type {
|
||||
Credentials,
|
||||
Keypair,
|
||||
OfferRequest,
|
||||
Offer,
|
||||
|
||||
@@ -111,7 +111,7 @@ export class RondevuSignaler implements Signaler {
|
||||
}
|
||||
|
||||
// Send answer to the service
|
||||
const result = await this.rondevu.getAPI().postOfferAnswer(this.serviceFqn, this.offerId, answer.sdp)
|
||||
const result = await this.rondevu.getAPIPublic().postOfferAnswer(this.serviceFqn, this.offerId, answer.sdp)
|
||||
this.offerId = result.offerId
|
||||
this.isOfferer = false
|
||||
|
||||
@@ -173,7 +173,7 @@ export class RondevuSignaler implements Signaler {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.rondevu.getAPI().addOfferIceCandidates(
|
||||
await this.rondevu.getAPIPublic().addOfferIceCandidates(
|
||||
this.serviceFqn,
|
||||
this.offerId,
|
||||
[candidateData]
|
||||
@@ -212,7 +212,7 @@ export class RondevuSignaler implements Signaler {
|
||||
try {
|
||||
// Get service by FQN (service should include @username)
|
||||
const serviceFqn = `${this.service}@${this.host}`
|
||||
const serviceData = await this.rondevu.getAPI().getService(serviceFqn)
|
||||
const serviceData = await this.rondevu.getAPIPublic().getService(serviceFqn)
|
||||
|
||||
if (!serviceData) {
|
||||
console.warn(`No service found for ${serviceFqn}`)
|
||||
@@ -390,7 +390,7 @@ export class RondevuSignaler implements Signaler {
|
||||
|
||||
try {
|
||||
const result = await this.rondevu
|
||||
.getAPI()
|
||||
.getAPIPublic()
|
||||
.getOfferIceCandidates(this.serviceFqn, this.offerId, this.lastPollTimestamp)
|
||||
|
||||
let foundCandidates = false
|
||||
|
||||
102
src/rondevu.ts
102
src/rondevu.ts
@@ -1,10 +1,9 @@
|
||||
import { RondevuAPI, Credentials, Keypair, Service, ServiceRequest, IceCandidate } from './api.js'
|
||||
import { RondevuAPI, Keypair, Service, ServiceRequest, IceCandidate } from './api.js'
|
||||
|
||||
export interface RondevuOptions {
|
||||
apiUrl: string
|
||||
username: string
|
||||
keypair?: Keypair
|
||||
credentials?: Credentials
|
||||
username?: string // Optional, will generate anonymous if not provided
|
||||
keypair?: Keypair // Optional, will generate if not provided
|
||||
}
|
||||
|
||||
export interface PublishServiceOptions {
|
||||
@@ -22,19 +21,24 @@ export interface PublishServiceOptions {
|
||||
* - Service discovery (direct, random, paginated)
|
||||
* - WebRTC signaling (offer/answer exchange, ICE relay)
|
||||
* - Keypair management
|
||||
* - Anonymous usage (auto-generates username and keypair)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Initialize (generates keypair automatically)
|
||||
* // Option 1: Named user (manually claim username)
|
||||
* const rondevu = new Rondevu({
|
||||
* apiUrl: 'https://signal.example.com',
|
||||
* username: 'alice',
|
||||
* })
|
||||
*
|
||||
* await rondevu.initialize()
|
||||
* await rondevu.claimUsername() // Claim username once
|
||||
*
|
||||
* // Claim username (one time)
|
||||
* await rondevu.claimUsername()
|
||||
* // Option 2: Anonymous user (auto-claims generated username)
|
||||
* const rondevu = new Rondevu({
|
||||
* apiUrl: 'https://signal.example.com',
|
||||
* // username omitted - will generate 'anon-xxxxx'
|
||||
* })
|
||||
* await rondevu.initialize() // Auto-claims anonymous username
|
||||
*
|
||||
* // Publish a service
|
||||
* const publishedService = await rondevu.publishService({
|
||||
@@ -51,15 +55,16 @@ export interface PublishServiceOptions {
|
||||
* ```
|
||||
*/
|
||||
export class Rondevu {
|
||||
private readonly api: RondevuAPI
|
||||
private readonly username: string
|
||||
private api: RondevuAPI | null = null
|
||||
private readonly apiUrl: string
|
||||
private username: string
|
||||
private keypair: Keypair | null = null
|
||||
private usernameClaimed = false
|
||||
|
||||
constructor(options: RondevuOptions) {
|
||||
this.username = options.username
|
||||
this.apiUrl = options.apiUrl
|
||||
this.username = options.username || this.generateAnonymousUsername()
|
||||
this.keypair = options.keypair || null
|
||||
this.api = new RondevuAPI(options.apiUrl, options.credentials)
|
||||
|
||||
console.log('[Rondevu] Constructor called:', {
|
||||
username: this.username,
|
||||
@@ -68,17 +73,29 @@ export class Rondevu {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an anonymous username with timestamp and random component
|
||||
*/
|
||||
private generateAnonymousUsername(): string {
|
||||
const timestamp = Date.now().toString(36)
|
||||
const random = Array.from(crypto.getRandomValues(new Uint8Array(3)))
|
||||
.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
return `anon-${timestamp}-${random}`
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Initialization
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Initialize the service - generates keypair if not provided
|
||||
* Initialize the service - generates keypair if not provided and creates API instance
|
||||
* Auto-claims username for anonymous users
|
||||
* Call this before using other methods
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
console.log('[Rondevu] Initialize called, hasKeypair:', !!this.keypair)
|
||||
|
||||
// Generate keypair if not provided
|
||||
if (!this.keypair) {
|
||||
console.log('[Rondevu] Generating new keypair...')
|
||||
this.keypair = await RondevuAPI.generateKeypair()
|
||||
@@ -87,10 +104,20 @@ export class Rondevu {
|
||||
console.log('[Rondevu] Using existing keypair, publicKey:', this.keypair.publicKey)
|
||||
}
|
||||
|
||||
// Register with API if no credentials provided
|
||||
if (!this.api['credentials']) {
|
||||
const credentials = await this.api.register()
|
||||
this.api.setCredentials(credentials)
|
||||
// Create API instance with username and keypair
|
||||
this.api = new RondevuAPI(this.apiUrl, this.username, this.keypair)
|
||||
console.log('[Rondevu] Created API instance with username:', this.username)
|
||||
|
||||
// Auto-claim username for anonymous users
|
||||
if (this.username.startsWith('anon-')) {
|
||||
console.log('[Rondevu] Auto-claiming anonymous username:', this.username)
|
||||
try {
|
||||
await this.claimUsername()
|
||||
console.log('[Rondevu] Successfully claimed anonymous username')
|
||||
} catch (error) {
|
||||
console.error('[Rondevu] Failed to claim anonymous username:', error)
|
||||
// Don't throw - allow the user to continue, they just won't be able to publish services
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +135,7 @@ export class Rondevu {
|
||||
}
|
||||
|
||||
// Check if username is already claimed
|
||||
const check = await this.api.checkUsername(this.username)
|
||||
const check = await this.getAPI().checkUsername(this.username)
|
||||
if (!check.available) {
|
||||
// Verify it's claimed by us
|
||||
if (check.publicKey === this.keypair.publicKey) {
|
||||
@@ -123,10 +150,20 @@ export class Rondevu {
|
||||
const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey)
|
||||
|
||||
// Claim the username
|
||||
await this.api.claimUsername(this.username, this.keypair.publicKey, signature, message)
|
||||
await this.getAPI().claimUsername(this.username, this.keypair.publicKey, signature, message)
|
||||
this.usernameClaimed = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API instance (creates lazily if needed)
|
||||
*/
|
||||
private getAPI(): RondevuAPI {
|
||||
if (!this.api) {
|
||||
throw new Error('Not initialized. Call initialize() first.')
|
||||
}
|
||||
return this.api
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if username has been claimed (checks with server)
|
||||
*/
|
||||
@@ -136,7 +173,7 @@ export class Rondevu {
|
||||
}
|
||||
|
||||
try {
|
||||
const check = await this.api.checkUsername(this.username)
|
||||
const check = await this.getAPI().checkUsername(this.username)
|
||||
|
||||
// Debug logging
|
||||
console.log('[Rondevu] Username check:', {
|
||||
@@ -194,7 +231,7 @@ export class Rondevu {
|
||||
}
|
||||
|
||||
// Publish to server
|
||||
return await this.api.publishService(serviceRequest)
|
||||
return await this.getAPI().publishService(serviceRequest)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -214,7 +251,7 @@ export class Rondevu {
|
||||
createdAt: number
|
||||
expiresAt: number
|
||||
}> {
|
||||
return await this.api.getService(serviceFqn)
|
||||
return await this.getAPI().getService(serviceFqn)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +267,7 @@ export class Rondevu {
|
||||
createdAt: number
|
||||
expiresAt: number
|
||||
}> {
|
||||
return await this.api.discoverService(serviceVersion)
|
||||
return await this.getAPI().discoverService(serviceVersion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +288,7 @@ export class Rondevu {
|
||||
limit: number
|
||||
offset: number
|
||||
}> {
|
||||
return await this.api.discoverServices(serviceVersion, limit, offset)
|
||||
return await this.getAPI().discoverServices(serviceVersion, limit, offset)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -265,7 +302,7 @@ export class Rondevu {
|
||||
success: boolean
|
||||
offerId: string
|
||||
}> {
|
||||
return await this.api.postOfferAnswer(serviceFqn, offerId, sdp)
|
||||
return await this.getAPI().postOfferAnswer(serviceFqn, offerId, sdp)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,7 +314,7 @@ export class Rondevu {
|
||||
answererId: string
|
||||
answeredAt: number
|
||||
} | null> {
|
||||
return await this.api.getOfferAnswer(serviceFqn, offerId)
|
||||
return await this.getAPI().getOfferAnswer(serviceFqn, offerId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +330,7 @@ export class Rondevu {
|
||||
answeredAt: number
|
||||
}>
|
||||
}> {
|
||||
return await this.api.getAnsweredOffers(since)
|
||||
return await this.getAPI().getAnsweredOffers(since)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,7 +352,7 @@ export class Rondevu {
|
||||
createdAt: number
|
||||
}>>
|
||||
}> {
|
||||
return await this.api.pollOffers(since)
|
||||
return await this.getAPI().pollOffers(since)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,7 +362,7 @@ export class Rondevu {
|
||||
count: number
|
||||
offerId: string
|
||||
}> {
|
||||
return await this.api.addOfferIceCandidates(serviceFqn, offerId, candidates)
|
||||
return await this.getAPI().addOfferIceCandidates(serviceFqn, offerId, candidates)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,7 +372,7 @@ export class Rondevu {
|
||||
candidates: IceCandidate[]
|
||||
offerId: string
|
||||
}> {
|
||||
return await this.api.getOfferIceCandidates(serviceFqn, offerId, since)
|
||||
return await this.getAPI().getOfferIceCandidates(serviceFqn, offerId, since)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -367,7 +404,10 @@ export class Rondevu {
|
||||
* Access to underlying API for advanced operations
|
||||
* @deprecated Use direct methods on Rondevu instance instead
|
||||
*/
|
||||
getAPI(): RondevuAPI {
|
||||
getAPIPublic(): RondevuAPI {
|
||||
if (!this.api) {
|
||||
throw new Error('Not initialized. Call initialize() first.')
|
||||
}
|
||||
return this.api
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user