@keypo/typescript-sdk
Version:
A TypeScript SDK for using Keypo
261 lines (260 loc) • 11.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.list = list;
async function list(address, debug, apiUrl, filter) {
// If localURL is provided, use it, otherwise use the default
const baseUrl = apiUrl || 'https://api.keypo.io';
const pageSize = filter?.pagination?.pageSize || 100;
const maxPages = filter?.pagination?.maxPages || Infinity;
if (debug) {
console.log("[DEBUG] Pagination settings:", { pageSize, maxPages });
}
// Helper function to fetch a single page of data
async function fetchPage(skip, isOwner) {
const endpoint = isOwner ? 'filesByOwner' : 'filesByMinter';
const url = `${baseUrl}/graph/${endpoint}?file${isOwner ? 'Owner' : 'Minter'}Address=${address}&skip=${skip}&first=${pageSize}`;
if (debug) {
console.log(`[DEBUG] Fetching ${endpoint} page at skip=${skip}:`, url);
}
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
return response.json();
}
// Helper function to check if files are deleted (batch version)
async function areFilesDeleted(fileIdentifiers) {
const url = `${baseUrl}/graph/isDeleted?fileIdentifiers=${encodeURIComponent(JSON.stringify(fileIdentifiers))}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const data = await response.json();
return data.deletedFiles || {};
}
// Helper function to fetch all pages for a given endpoint
async function fetchAllPages(isOwner) {
const allData = { permissionedFileDeployeds: [], permissionedFileAccessMinteds: [] };
let skip = 0;
let page = 0;
let hasMore = true;
while (hasMore && page < maxPages) {
const pageData = await fetchPage(skip, isOwner);
if (debug) {
console.log(`[DEBUG] Got ${isOwner ? 'owner' : 'minter'} page ${page + 1}:`, pageData);
}
// Add the page data to our collection
allData.permissionedFileDeployeds = [
...allData.permissionedFileDeployeds,
...(pageData.permissionedFileDeployeds || [])
];
allData.permissionedFileAccessMinteds = [
...allData.permissionedFileAccessMinteds,
...(pageData.permissionedFileAccessMinteds || [])
];
// Check if we have more pages
hasMore = (pageData.permissionedFileDeployeds || []).length === pageSize;
skip += pageSize;
page++;
if (debug) {
console.log(`[DEBUG] Processed ${isOwner ? 'owner' : 'minter'} page ${page}. Has more:`, hasMore);
}
}
return allData;
}
// Fetch both owner and minter data with pagination
const [ownerData, minterData] = await Promise.all([
fetchAllPages(true),
fetchAllPages(false)
]);
if (debug) {
console.log("[DEBUG] Total owner files:", ownerData.permissionedFileDeployeds.length);
console.log("[DEBUG] Total minter files:", minterData.permissionedFileDeployeds.length);
console.log("[DEBUG] Total owner access minted files:", ownerData.permissionedFileAccessMinteds.length);
console.log("[DEBUG] Total minter access minted files:", minterData.permissionedFileAccessMinteds.length);
}
// Helper function to extract metadata fields
const extractMetadata = (fileMetadata) => {
// If metadata is in userMetaData, use that
/*if (fileMetadata.userMetaData) {
return {
name: fileMetadata.userMetaData.name,
type: fileMetadata.userMetaData.type,
mimeType: fileMetadata.userMetaData.mimeType,
subtype: fileMetadata.userMetaData.subtype,
userMetaData: JSON.stringify(fileMetadata.userMetaData)
};
}*/
// Otherwise use the root fields
return {
name: fileMetadata.name,
type: fileMetadata.type,
mimeType: fileMetadata.mimeType,
subtype: fileMetadata.subtype,
userMetaData: fileMetadata.userMetaData ? JSON.stringify(fileMetadata.userMetaData) : undefined
};
};
// Helper function to check if a file matches the filter
const matchesFilter = (file) => {
if (!filter?.filterBy)
return true;
const fieldValue = file.dataMetadata[filter.filterBy.field];
if (fieldValue === undefined)
return false;
switch (filter.filterBy.operator) {
case 'contains':
return String(fieldValue).toLowerCase().includes(String(filter.filterBy.value).toLowerCase());
case 'startsWith':
return String(fieldValue).toLowerCase().startsWith(String(filter.filterBy.value).toLowerCase());
case 'endsWith':
return String(fieldValue).toLowerCase().endsWith(String(filter.filterBy.value).toLowerCase());
case 'equals':
default:
return String(fieldValue).toLowerCase() === String(filter.filterBy.value).toLowerCase();
}
};
// Process all files (both deployed and access minted)
const allFiles = {};
const processedFileIds = new Set();
// Process deployed files from owner endpoint
for (const file of ownerData.permissionedFileDeployeds || []) {
const dataIdentifier = file.fileIdentifier.toLowerCase();
if (!processedFileIds.has(dataIdentifier)) {
const fileMetadata = JSON.parse(file.fileMetadata);
const fileData = {
cid: fileMetadata.encryptedData.ipfsHash,
dataContractAddress: file.fileContractAddress,
dataIdentifier: dataIdentifier,
dataMetadata: extractMetadata(fileMetadata),
owner: file.fileOwner,
isAccessMinted: false
};
if (matchesFilter(fileData)) {
allFiles[dataIdentifier] = fileData;
processedFileIds.add(dataIdentifier);
}
}
}
// Process access minted files from owner endpoint
for (const file of ownerData.permissionedFileAccessMinteds || []) {
const dataIdentifier = file.fileIdentifier.toLowerCase();
if (!processedFileIds.has(dataIdentifier)) {
const fileMetadata = JSON.parse(file.fileMetadata);
const fileData = {
cid: fileMetadata.encryptedData.ipfsHash,
dataContractAddress: file.fileContractAddress,
dataIdentifier: dataIdentifier,
dataMetadata: extractMetadata(fileMetadata),
owner: file.fileOwner,
isAccessMinted: true
};
if (matchesFilter(fileData)) {
allFiles[dataIdentifier] = fileData;
processedFileIds.add(dataIdentifier);
}
}
}
// Process deployed files from minter endpoint
for (const file of minterData.permissionedFileDeployeds || []) {
const dataIdentifier = file.fileIdentifier.toLowerCase();
if (!processedFileIds.has(dataIdentifier)) {
const fileMetadata = JSON.parse(file.fileMetadata);
const fileData = {
cid: fileMetadata.encryptedData.ipfsHash,
dataContractAddress: file.fileContractAddress,
dataIdentifier: dataIdentifier,
dataMetadata: extractMetadata(fileMetadata),
owner: file.fileOwner,
isAccessMinted: false
};
if (matchesFilter(fileData)) {
allFiles[dataIdentifier] = fileData;
processedFileIds.add(dataIdentifier);
}
}
}
// Process access minted files from minter endpoint
for (const file of minterData.permissionedFileAccessMinteds || []) {
const dataIdentifier = file.fileIdentifier.toLowerCase();
if (!processedFileIds.has(dataIdentifier)) {
const fileMetadata = JSON.parse(file.fileMetadata);
const fileData = {
cid: fileMetadata.encryptedData.ipfsHash,
dataContractAddress: file.fileContractAddress,
dataIdentifier: dataIdentifier,
dataMetadata: extractMetadata(fileMetadata),
owner: file.fileOwner,
isAccessMinted: true
};
if (matchesFilter(fileData)) {
allFiles[dataIdentifier] = fileData;
processedFileIds.add(dataIdentifier);
}
}
}
// Filter out deleted files using batch request
const finalFiles = {};
try {
// Get all file identifiers
const allFileIdentifiers = Object.keys(allFiles);
if (debug) {
console.log(`[DEBUG] Checking deletion status for ${allFileIdentifiers.length} files`);
}
if (allFileIdentifiers.length > 0) {
// Batch check deletion status
const deletionStatuses = await areFilesDeleted(allFileIdentifiers);
// Filter out deleted files
Object.entries(allFiles).forEach(([dataIdentifier, fileData]) => {
const isDeleted = deletionStatuses[dataIdentifier] || false;
if (!isDeleted) {
finalFiles[dataIdentifier] = fileData;
}
else if (debug) {
console.log(`[DEBUG] Filtered out deleted file: ${dataIdentifier}`);
}
});
if (debug) {
console.log(`[DEBUG] Files after deletion filter: ${Object.keys(finalFiles).length}`);
}
}
}
catch (error) {
console.warn('Failed to check deletion status, including all files:', error);
// If batch deletion check fails, include all files
Object.assign(finalFiles, allFiles);
}
// Apply sorting if specified
if (filter?.sortBy) {
const sortBy = filter.sortBy;
const sortedEntries = Object.entries(finalFiles).sort(([, a], [, b]) => {
const aValue = a.dataMetadata[sortBy.field];
const bValue = b.dataMetadata[sortBy.field];
// Handle undefined values
if (aValue === undefined && bValue === undefined)
return 0;
if (aValue === undefined)
return sortBy.direction === 'desc' ? 1 : -1;
if (bValue === undefined)
return sortBy.direction === 'desc' ? -1 : 1;
// Compare values
const comparison = String(aValue).localeCompare(String(bValue));
return sortBy.direction === 'desc' ? -comparison : comparison;
});
// Convert sorted entries back to object
return Object.fromEntries(sortedEntries);
}
if (debug) {
console.log("[DEBUG] Final combined data:", finalFiles);
}
return finalFiles;
}