feat: Add IPNS support
This commit is contained in:
@@ -9,7 +9,8 @@ const {
|
||||
LOCAL_RESOLVER_HOST,
|
||||
LOCAL_RESOLVER_PORT,
|
||||
CACHE_ENABLED,
|
||||
CACHE_TTL_SECONDS
|
||||
CACHE_TTL_SECONDS,
|
||||
IPFS_GATEWAY
|
||||
} = config;
|
||||
|
||||
// Setup cache
|
||||
@@ -18,6 +19,12 @@ const cache = new NodeCache({
|
||||
checkperiod: CACHE_TTL_SECONDS * 0.2,
|
||||
});
|
||||
|
||||
// Setup separate cache for IPNS with shorter TTL (IPNS can change more frequently)
|
||||
const ipnsCache = new NodeCache({
|
||||
stdTTL: Math.min(CACHE_TTL_SECONDS, 300), // Max 5 minutes for IPNS
|
||||
checkperiod: 60,
|
||||
});
|
||||
|
||||
/**
|
||||
* Resolve a Handshake domain to an IPFS CID
|
||||
* @param {string} domain - The Handshake domain to resolve
|
||||
@@ -299,14 +306,14 @@ async function resolveLocal(domain) {
|
||||
/**
|
||||
* Extract IPFS CID from DNS TXT records
|
||||
* @param {string[][]} records - Array of TXT record arrays
|
||||
* @returns {string|null} - IPFS CID or null
|
||||
* @returns {Promise<string|null>} - IPFS CID or IPNS hash or null
|
||||
*/
|
||||
function extractCidFromRecords(records) {
|
||||
async function extractCidFromRecords(records) {
|
||||
if (!records || !records.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Flatten and look for ipfs= or ip6= prefixes
|
||||
// Flatten and look for ipfs=, ip6=, or ipns= prefixes
|
||||
for (const recordSet of records) {
|
||||
for (const record of recordSet) {
|
||||
// Support multiple formats
|
||||
@@ -319,6 +326,18 @@ function extractCidFromRecords(records) {
|
||||
if (record.startsWith('ip6=')) {
|
||||
return record.substring(4);
|
||||
}
|
||||
if (record.startsWith('ipns=')) {
|
||||
const ipnsName = record.substring(5);
|
||||
console.log(`Found IPNS record: ${ipnsName}`);
|
||||
// Return IPNS hash prefixed with 'ipns:' to distinguish from IPFS CID
|
||||
return `ipns:${ipnsName}`;
|
||||
}
|
||||
if (record.startsWith('ipns:')) {
|
||||
const ipnsName = record.substring(5);
|
||||
console.log(`Found IPNS record: ${ipnsName}`);
|
||||
// Return IPNS hash prefixed with 'ipns:' to distinguish from IPFS CID
|
||||
return `ipns:${ipnsName}`;
|
||||
}
|
||||
|
||||
// Log the record for debugging
|
||||
console.log(`Found TXT record: ${record}`);
|
||||
@@ -328,7 +347,6 @@ function extractCidFromRecords(records) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the cache for a specific domain
|
||||
* @param {string} domain - The Handshake domain to clear from cache
|
||||
@@ -340,7 +358,46 @@ function clearCache(domain) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear IPNS cache for a specific IPNS name (keeping for API compatibility)
|
||||
* @param {string} ipnsName - The IPNS name to clear from cache
|
||||
*/
|
||||
function clearIpnsCache(ipnsName) {
|
||||
// Since we're not caching IPNS resolutions anymore, just log
|
||||
console.log(`IPNS cache clear requested for ${ipnsName} (no-op)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache for a specific domain
|
||||
* @param {string} domain - The Handshake domain to clear from cache
|
||||
*/
|
||||
function clearCache(domain) {
|
||||
if (CACHE_ENABLED) {
|
||||
cache.del(`hns:${domain}`);
|
||||
// Also clear any IPNS cache entries that might be related
|
||||
const keys = ipnsCache.keys();
|
||||
keys.forEach(key => {
|
||||
if (key.includes(domain)) {
|
||||
ipnsCache.del(key);
|
||||
}
|
||||
});
|
||||
console.log(`Cache cleared for ${domain}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear IPNS cache for a specific IPNS name
|
||||
* @param {string} ipnsName - The IPNS name to clear from cache
|
||||
*/
|
||||
function clearIpnsCache(ipnsName) {
|
||||
if (CACHE_ENABLED) {
|
||||
ipnsCache.del(`ipns:${ipnsName}`);
|
||||
console.log(`IPNS cache cleared for ${ipnsName}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveHandshake,
|
||||
clearCache
|
||||
clearCache,
|
||||
clearIpnsCache
|
||||
};
|
||||
|
||||
38
lib/ipfs.js
38
lib/ipfs.js
@@ -24,26 +24,30 @@ const mimeTypes = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch content from IPFS by CID and path
|
||||
* @param {string} cid - IPFS Content Identifier
|
||||
* @param {string} path - Optional path within the CID
|
||||
* Fetch content from IPFS by CID and path, or from IPNS
|
||||
* @param {string} cidOrIpns - IPFS Content Identifier or IPNS hash (prefixed with 'ipns:')
|
||||
* @param {string} path - Optional path within the CID/IPNS
|
||||
* @returns {Promise<{data: Buffer, mimeType: string}|null>} - Content and MIME type or null
|
||||
*/
|
||||
async function fetchFromIpfs(cid, path = '') {
|
||||
const contentPath = path ? `${cid}/${path}` : cid;
|
||||
async function fetchFromIpfs(cidOrIpns, path = '') {
|
||||
const isIpns = cidOrIpns.startsWith('ipns:');
|
||||
const hash = isIpns ? cidOrIpns.substring(5) : cidOrIpns;
|
||||
const contentType = isIpns ? 'ipns' : 'ipfs';
|
||||
const contentPath = path ? `${hash}/${path}` : hash;
|
||||
|
||||
// Check cache first
|
||||
if (CACHE_ENABLED) {
|
||||
const cachedContent = cache.get(`ipfs:${contentPath}`);
|
||||
const cacheKey = `${contentType}:${contentPath}`;
|
||||
const cachedContent = cache.get(cacheKey);
|
||||
if (cachedContent) {
|
||||
console.log(`Cache hit for IPFS content: ${contentPath}`);
|
||||
console.log(`Cache hit for ${contentType.toUpperCase()} content: ${contentPath}`);
|
||||
return cachedContent;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Use the HTTP gateway directly instead of the IPFS client
|
||||
const result = await fetchViaGateway(cid, path);
|
||||
// Use the HTTP gateway directly
|
||||
const result = await fetchViaGateway(hash, path, contentType);
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
@@ -56,25 +60,29 @@ async function fetchFromIpfs(cid, path = '') {
|
||||
|
||||
// Cache the result
|
||||
if (CACHE_ENABLED) {
|
||||
cache.set(`ipfs:${contentPath}`, result);
|
||||
const cacheKey = `${contentType}:${contentPath}`;
|
||||
// Use shorter TTL for IPNS content since it can change
|
||||
const ttl = isIpns ? Math.min(CACHE_TTL_SECONDS, 300) : CACHE_TTL_SECONDS;
|
||||
cache.set(cacheKey, result, ttl);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching ${contentPath} from IPFS:`, error);
|
||||
console.error(`Error fetching ${contentPath} from ${contentType.toUpperCase()}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch content via IPFS HTTP gateway
|
||||
* @param {string} cid - IPFS Content Identifier
|
||||
* @param {string} path - Path within the CID
|
||||
* @param {string} hash - IPFS CID or IPNS hash
|
||||
* @param {string} path - Path within the content
|
||||
* @param {string} contentType - Either 'ipfs' or 'ipns'
|
||||
* @returns {Promise<{data: Buffer, mimeType: string}|null>} - Content and MIME type or null
|
||||
*/
|
||||
async function fetchViaGateway(cid, path) {
|
||||
async function fetchViaGateway(hash, path, contentType = 'ipfs') {
|
||||
try {
|
||||
const url = new URL(`${IPFS_GATEWAY}/ipfs/${cid}${path ? '/' + path : ''}`);
|
||||
const url = new URL(`${IPFS_GATEWAY}/${contentType}/${hash}${path ? '/' + path : ''}`);
|
||||
console.log(`Fetching from IPFS gateway: ${url}`);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
Reference in New Issue
Block a user