You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

591 lines
20 KiB

/**
* API helper functions for ORLY relay management endpoints
*/
import { getApiBase } from './config.js';
/**
* Create NIP-98 authentication header
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} method - HTTP method
* @param {string} url - Request URL
* @returns {Promise<string|null>} Base64 encoded auth header or null
*/
export async function createNIP98Auth(signer, pubkey, method, url) {
if (!signer || !pubkey) {
console.log("createNIP98Auth: No signer or pubkey available", { hasSigner: !!signer, hasPubkey: !!pubkey });
return null;
}
try {
// Create unsigned auth event
const authEvent = {
kind: 27235,
created_at: Math.floor(Date.now() / 1000),
tags: [
["u", url],
["method", method.toUpperCase()],
],
content: "",
};
console.log("createNIP98Auth: Signing event for", method, url);
// Sign using the signer
const signedEvent = await signer.signEvent(authEvent);
console.log("createNIP98Auth: Signed event:", {
id: signedEvent.id,
pubkey: signedEvent.pubkey,
kind: signedEvent.kind,
created_at: signedEvent.created_at,
tags: signedEvent.tags,
hasSig: !!signedEvent.sig
});
// Use standard base64 encoding per BUD-01/NIP-98 spec
const json = JSON.stringify(signedEvent);
const base64 = btoa(json);
return base64;
} catch (error) {
console.error("createNIP98Auth: Error:", error);
return null;
}
}
/**
* Fetch user role from the relay
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<string>} User role
*/
export async function fetchUserRole(signer, pubkey) {
try {
const url = `${getApiBase()}/api/role`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (response.ok) {
const data = await response.json();
return data.role || "";
}
} catch (error) {
console.error("Error fetching user role:", error);
}
return "";
}
/**
* Fetch ACL mode from the relay
* @returns {Promise<string>} ACL mode
*/
export async function fetchACLMode() {
try {
const response = await fetch(`${getApiBase()}/api/acl-mode`);
if (response.ok) {
const data = await response.json();
return data.mode || "";
}
} catch (error) {
console.error("Error fetching ACL mode:", error);
}
return "";
}
// ==================== Sprocket API ====================
/**
* Load sprocket configuration
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Sprocket config data
*/
export async function loadSprocketConfig(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket/config`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load config: ${response.statusText}`);
return await response.json();
}
/**
* Load sprocket status
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Sprocket status data
*/
export async function loadSprocketStatus(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket/status`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load status: ${response.statusText}`);
return await response.json();
}
/**
* Load sprocket script
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<string>} Sprocket script content
*/
export async function loadSprocketScript(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (response.status === 404) return "";
if (!response.ok) throw new Error(`Failed to load sprocket: ${response.statusText}`);
return await response.text();
}
/**
* Save sprocket script
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} script - Script content
* @returns {Promise<object>} Save result
*/
export async function saveSprocketScript(signer, pubkey, script) {
const url = `${getApiBase()}/api/sprocket`;
const authHeader = await createNIP98Auth(signer, pubkey, "PUT", url);
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "text/plain",
...(authHeader ? { Authorization: `Nostr ${authHeader}` } : {}),
},
body: script,
});
if (!response.ok) throw new Error(`Failed to save: ${response.statusText}`);
return await response.json();
}
/**
* Restart sprocket
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Restart result
*/
export async function restartSprocket(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket/restart`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const response = await fetch(url, {
method: "POST",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to restart: ${response.statusText}`);
return await response.json();
}
/**
* Delete sprocket
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Delete result
*/
export async function deleteSprocket(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket`;
const authHeader = await createNIP98Auth(signer, pubkey, "DELETE", url);
const response = await fetch(url, {
method: "DELETE",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to delete: ${response.statusText}`);
return await response.json();
}
/**
* Load sprocket versions
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<Array>} Version list
*/
export async function loadSprocketVersions(signer, pubkey) {
const url = `${getApiBase()}/api/sprocket/versions`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load versions: ${response.statusText}`);
return await response.json();
}
/**
* Load specific sprocket version
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} version - Version filename
* @returns {Promise<string>} Version content
*/
export async function loadSprocketVersion(signer, pubkey, version) {
const url = `${getApiBase()}/api/sprocket/versions/${encodeURIComponent(version)}`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load version: ${response.statusText}`);
return await response.text();
}
/**
* Delete sprocket version
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} filename - Version filename
* @returns {Promise<object>} Delete result
*/
export async function deleteSprocketVersion(signer, pubkey, filename) {
const url = `${getApiBase()}/api/sprocket/versions/${encodeURIComponent(filename)}`;
const authHeader = await createNIP98Auth(signer, pubkey, "DELETE", url);
const response = await fetch(url, {
method: "DELETE",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to delete version: ${response.statusText}`);
return await response.json();
}
/**
* Upload sprocket script file
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {File} file - File to upload
* @returns {Promise<object>} Upload result
*/
export async function uploadSprocketScript(signer, pubkey, file) {
const content = await file.text();
return await saveSprocketScript(signer, pubkey, content);
}
// ==================== Policy API ====================
/**
* Load policy configuration
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Policy config
*/
export async function loadPolicyConfig(signer, pubkey) {
const url = `${getApiBase()}/api/policy/config`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load policy config: ${response.statusText}`);
return await response.json();
}
/**
* Load policy JSON
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Policy JSON
*/
export async function loadPolicy(signer, pubkey) {
const url = `${getApiBase()}/api/policy`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to load policy: ${response.statusText}`);
return await response.json();
}
/**
* Validate policy JSON
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} policyJson - Policy JSON string
* @returns {Promise<object>} Validation result
*/
export async function validatePolicy(signer, pubkey, policyJson) {
const url = `${getApiBase()}/api/policy/validate`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(authHeader ? { Authorization: `Nostr ${authHeader}` } : {}),
},
body: policyJson,
});
return await response.json();
}
/**
* Fetch policy follows whitelist
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<Array>} List of followed pubkeys
*/
export async function fetchPolicyFollows(signer, pubkey) {
const url = `${getApiBase()}/api/policy/follows`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) throw new Error(`Failed to fetch follows: ${response.statusText}`);
const data = await response.json();
return data.follows || [];
}
// ==================== Relay Info API ====================
/**
* Fetch relay info document (NIP-11)
* @returns {Promise<object>} Relay info including version
*/
export async function fetchRelayInfo() {
try {
const response = await fetch(getApiBase(), {
headers: {
Accept: "application/nostr+json",
},
});
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("Error fetching relay info:", error);
}
return null;
}
// ==================== Export/Import API ====================
/**
* Export events
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {Array} authorPubkeys - Filter by authors (empty for all)
* @returns {Promise<Blob>} JSONL blob
*/
export async function exportEvents(signer, pubkey, authorPubkeys = []) {
const url = `${getApiBase()}/api/export`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(authHeader ? { Authorization: `Nostr ${authHeader}` } : {}),
},
body: JSON.stringify({ pubkeys: authorPubkeys }),
});
if (!response.ok) throw new Error(`Export failed: ${response.statusText}`);
return await response.blob();
}
/**
* Import events from file
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {File} file - JSONL file to import
* @returns {Promise<object>} Import result
*/
export async function importEvents(signer, pubkey, file) {
const url = `${getApiBase()}/api/import`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const formData = new FormData();
formData.append("file", file);
const response = await fetch(url, {
method: "POST",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
body: formData,
});
if (!response.ok) throw new Error(`Import failed: ${response.statusText}`);
return await response.json();
}
// ==================== WireGuard API ====================
/**
* Fetch WireGuard status
* @returns {Promise<object>} WireGuard status
*/
export async function fetchWireGuardStatus() {
try {
const response = await fetch(`${getApiBase()}/api/wireguard/status`);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("Error fetching WireGuard status:", error);
}
return { wireguard_enabled: false, bunker_enabled: false, available: false };
}
/**
* Get WireGuard configuration for the authenticated user
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} WireGuard config
*/
export async function getWireGuardConfig(signer, pubkey) {
const url = `${getApiBase()}/api/wireguard/config`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to get WireGuard config: ${response.statusText}`);
}
return await response.json();
}
/**
* Regenerate WireGuard keypair for the authenticated user
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Regeneration result
*/
export async function regenerateWireGuard(signer, pubkey) {
const url = `${getApiBase()}/api/wireguard/regenerate`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const response = await fetch(url, {
method: "POST",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to regenerate WireGuard: ${response.statusText}`);
}
return await response.json();
}
/**
* Get WireGuard audit log (revoked keys and access attempts)
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Audit data with revoked_keys and access_logs
*/
export async function getWireGuardAudit(signer, pubkey) {
const url = `${getApiBase()}/api/wireguard/audit`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to get audit log: ${response.statusText}`);
}
return await response.json();
}
// ==================== NRC (Nostr Relay Connect) API ====================
/**
* Get NRC configuration status (no auth required)
* @returns {Promise<object>} NRC config status
*/
export async function fetchNRCConfig() {
const apiBase = getApiBase();
console.log("[api] fetchNRCConfig using base URL:", apiBase);
try {
const response = await fetch(`${apiBase}/api/nrc/config`);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("Error fetching NRC config:", error);
}
return { enabled: false, badger_required: true };
}
/**
* Get all NRC connections
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @returns {Promise<object>} Connections list and config
*/
export async function fetchNRCConnections(signer, pubkey) {
const url = `${getApiBase()}/api/nrc/connections`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to get NRC connections: ${response.statusText}`);
}
return await response.json();
}
/**
* Create a new NRC connection
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} label - Connection label
* @returns {Promise<object>} Created connection with URI
*/
export async function createNRCConnection(signer, pubkey, label) {
const url = `${getApiBase()}/api/nrc/connections`;
const authHeader = await createNIP98Auth(signer, pubkey, "POST", url);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(authHeader ? { Authorization: `Nostr ${authHeader}` } : {}),
},
body: JSON.stringify({ label }),
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to create NRC connection: ${response.statusText}`);
}
return await response.json();
}
/**
* Delete an NRC connection
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} connId - Connection ID to delete
* @returns {Promise<object>} Delete result
*/
export async function deleteNRCConnection(signer, pubkey, connId) {
const url = `${getApiBase()}/api/nrc/connections/${connId}`;
const authHeader = await createNIP98Auth(signer, pubkey, "DELETE", url);
const response = await fetch(url, {
method: "DELETE",
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to delete NRC connection: ${response.statusText}`);
}
return await response.json();
}
/**
* Get connection URI for an NRC connection
* @param {object} signer - The signer instance
* @param {string} pubkey - User's pubkey
* @param {string} connId - Connection ID
* @returns {Promise<object>} Connection URI
*/
export async function getNRCConnectionURI(signer, pubkey, connId) {
const url = `${getApiBase()}/api/nrc/connections/${connId}/uri`;
const authHeader = await createNIP98Auth(signer, pubkey, "GET", url);
const response = await fetch(url, {
headers: authHeader ? { Authorization: `Nostr ${authHeader}` } : {},
});
if (!response.ok) {
const error = await response.text();
throw new Error(error || `Failed to get NRC URI: ${response.statusText}`);
}
return await response.json();
}