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>
188 lines
5.5 KiB
TypeScript
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)));
|
|
}
|