SkateSpots/src/lib/relatr.ts
shakespeare.diy e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00

188 lines
5.5 KiB
TypeScript

/**
* Relatr API Integration
*
* Relatr is a decentralized Web of Trust service that provides global trust scores
* for Nostr pubkeys. This module provides integration with the Relatr service.
*
* Documentation: https://www.relatr.xyz/
* GitHub: https://github.com/ContextVM/relatr
*/
export interface RelatrScore {
source: string; // Source pubkey (perspective)
target: string; // Target pubkey being scored
score: number; // Trust score (0-100)
distance?: number; // Social graph distance
hasNip05?: boolean; // NIP-05 verification status
hasLud16?: boolean; // Lightning address status
timestamp: number; // When score was calculated
}
export interface RelatrBatchRequest {
source: string; // Perspective pubkey
targets: string[]; // Array of pubkeys to score
}
export interface RelatrBatchResponse {
scores: Map<string, RelatrScore>;
}
/**
* Relatr API client
*
* NOTE: This is a placeholder implementation. The actual Relatr API
* endpoint structure needs to be confirmed from the official documentation.
*
* Relatr provides an MCP (Model Context Protocol) server interface, which
* may require different integration patterns than a traditional REST API.
*
* Potential API patterns:
* 1. REST API: https://api.relatr.xyz/v1/scores?source=...&target=...
* 2. MCP Server: Requires MCP client connection
* 3. Nostr Events: Trust scores published as Nostr events
*/
export class RelatrClient {
private apiUrl: string;
private corsProxy: string;
constructor(
apiUrl = 'https://api.relatr.xyz',
corsProxy = 'https://proxy.shakespeare.diy/?url='
) {
this.apiUrl = apiUrl;
this.corsProxy = corsProxy;
}
/**
* Fetch trust score for a single target from source's perspective
*
* @param source - Source pubkey (viewer's perspective)
* @param target - Target pubkey to score
* @returns Trust score or null if unavailable
*/
async getScore(source: string, target: string): Promise<RelatrScore | null> {
try {
// TODO: Confirm actual Relatr API endpoint structure
// This is a placeholder based on common REST patterns
const url = `${this.corsProxy}${encodeURIComponent(
`${this.apiUrl}/v1/score?source=${source}&target=${target}`
)}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
console.warn(`[Relatr] API returned ${response.status}`);
return null;
}
const data = await response.json();
return {
source,
target,
score: data.score || 0,
distance: data.distance,
hasNip05: data.hasNip05,
hasLud16: data.hasLud16,
timestamp: Date.now(),
};
} catch (error) {
console.warn('[Relatr] Failed to fetch score:', error);
return null;
}
}
/**
* Fetch trust scores for multiple targets in batch
*
* @param source - Source pubkey (viewer's perspective)
* @param targets - Array of target pubkeys to score
* @returns Map of target pubkey to score
*/
async getBatchScores(
source: string,
targets: string[]
): Promise<Map<string, RelatrScore>> {
const scores = new Map<string, RelatrScore>();
try {
// TODO: Confirm if Relatr supports batch requests
// Option 1: Batch endpoint
const url = `${this.corsProxy}${encodeURIComponent(`${this.apiUrl}/v1/batch`)}`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ source, targets }),
signal: AbortSignal.timeout(15000),
});
if (!response.ok) {
console.warn(`[Relatr] Batch API returned ${response.status}`);
return scores;
}
const data = await response.json();
// Parse batch response
if (Array.isArray(data.scores)) {
for (const scoreData of data.scores) {
scores.set(scoreData.target, {
source,
target: scoreData.target,
score: scoreData.score || 0,
distance: scoreData.distance,
hasNip05: scoreData.hasNip05,
hasLud16: scoreData.hasLud16,
timestamp: Date.now(),
});
}
}
return scores;
} catch (error) {
console.warn('[Relatr] Batch request failed, falling back to individual requests:', error);
// Fallback: Make individual requests (slower but reliable)
for (const target of targets.slice(0, 10)) { // Limit to 10 to avoid rate limits
const score = await this.getScore(source, target);
if (score) {
scores.set(target, score);
}
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 100));
}
return scores;
}
}
}
/**
* Global Relatr client instance
*
* NOTE: Set RELATR_ENABLED = false until API integration is confirmed
*/
export const RELATR_ENABLED = false; // Toggle when API is ready
export const relatrClient = new RelatrClient();
/**
* Normalize Relatr score to 0-100 scale
* Relatr may use different scoring systems, this ensures compatibility
*/
export function normalizeRelatrScore(score: number): number {
// Assume Relatr uses 0-1 scale, convert to 0-100
if (score <= 1) {
return Math.round(score * 100);
}
// Already 0-100 scale
return Math.round(Math.max(0, Math.min(100, score)));
}