anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
219 lines • 8.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DelegationChainVisualizer = void 0;
class DelegationChainVisualizer {
/**
* Creates a tree structure from a delegation chain
*/
static createVisualization(chain, rootDID, rootName = 'Root User') {
const root = {
id: rootDID,
name: rootName,
type: 'user',
depth: 0,
canDelegate: true,
scopes: this.extractRootScopes(chain),
children: []
};
const scopeHierarchy = new Map();
let currentParent = root;
// Build tree structure
for (let i = 0; i < chain.agents.length; i++) {
const agent = chain.agents[i];
const credential = chain.credentials[i];
const node = {
id: agent.did,
name: agent.name,
type: 'agent',
depth: agent.delegationDepth + 1,
canDelegate: agent.canDelegate,
scopes: credential.credentialSubject.scopes,
children: []
};
// Track scope hierarchy
scopeHierarchy.set(agent.did, credential.credentialSubject.scopes);
// Find correct parent in tree
if (i === 0) {
root.children.push(node);
}
else {
const parent = this.findNodeById(root, agent.parentDID);
if (parent) {
parent.children.push(node);
}
}
}
return {
root,
totalDepth: chain.currentDepth,
totalAgents: chain.agents.length,
scopeHierarchy
};
}
/**
* Generates a text-based tree visualization
*/
static toAsciiTree(visualization) {
const lines = [];
this.buildAsciiTree(visualization.root, lines, '', true);
lines.push('');
lines.push(`Total Depth: ${visualization.totalDepth}`);
lines.push(`Total Agents: ${visualization.totalAgents}`);
return lines.join('\n');
}
/**
* Generates a Mermaid diagram for the delegation chain
*/
static toMermaidDiagram(visualization) {
const lines = ['graph TD'];
const nodeDefinitions = [];
const connections = [];
// Create node definitions and connections
this.buildMermaidNodes(visualization.root, nodeDefinitions, connections);
// Add node definitions
lines.push(...nodeDefinitions);
lines.push('');
// Add connections
lines.push(...connections);
// Add styling
lines.push('');
lines.push('classDef userNode fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff;');
lines.push('classDef agentNode fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff;');
lines.push('classDef cannotDelegate fill:#FF9800,stroke:#E65100,stroke-width:2px,color:#fff;');
// Apply styles
lines.push('');
const userNodes = this.findNodesByType(visualization.root, 'user');
if (userNodes.length > 0) {
lines.push(`class ${userNodes.map(n => this.sanitizeId(n.id)).join(',')} userNode;`);
}
const delegatingAgents = this.findNodesByCondition(visualization.root, n => n.type === 'agent' && n.canDelegate);
if (delegatingAgents.length > 0) {
lines.push(`class ${delegatingAgents.map(n => this.sanitizeId(n.id)).join(',')} agentNode;`);
}
const nonDelegatingAgents = this.findNodesByCondition(visualization.root, n => n.type === 'agent' && !n.canDelegate);
if (nonDelegatingAgents.length > 0) {
lines.push(`class ${nonDelegatingAgents.map(n => this.sanitizeId(n.id)).join(',')} cannotDelegate;`);
}
return lines.join('\n');
}
/**
* Generates a JSON representation suitable for D3.js or other visualization libraries
*/
static toD3Json(visualization) {
return {
name: visualization.root.name,
value: visualization.root.scopes.length,
data: {
id: visualization.root.id,
type: visualization.root.type,
canDelegate: visualization.root.canDelegate,
scopes: visualization.root.scopes
},
children: visualization.root.children.map(child => this.nodeToD3Json(child))
};
}
/**
* Analyzes scope reduction through the chain
*/
static analyzeScopeReduction(chain) {
const reductions = [];
let previousScopes = this.extractRootScopes(chain);
let totalScopeLoss = 0;
for (let i = 0; i < chain.credentials.length; i++) {
const credential = chain.credentials[i];
const currentScopes = credential.credentialSubject.scopes;
const removed = previousScopes.filter(s => !currentScopes.includes(s));
const retained = currentScopes.filter(s => previousScopes.includes(s));
if (removed.length > 0) {
reductions.push({
from: i === 0 ? 'Root' : chain.agents[i - 1].name,
to: chain.agents[i].name,
removed,
retained
});
totalScopeLoss += removed.length;
}
previousScopes = currentScopes;
}
return { reductions, totalScopeLoss };
}
// Helper methods
static extractRootScopes(chain) {
if (chain.credentials.length === 0)
return [];
// Assume root has all scopes that the first credential was issued with
// In practice, this might come from a different source
return chain.credentials[0].credentialSubject.scopes;
}
static findNodeById(node, id) {
if (node.id === id)
return node;
for (const child of node.children) {
const found = this.findNodeById(child, id);
if (found)
return found;
}
return null;
}
static buildAsciiTree(node, lines, prefix, isLast) {
const connector = isLast ? '└── ' : '├── ';
const nodeType = node.type === 'user' ? '[USER]' : '[AGENT]';
const delegateFlag = node.canDelegate ? '✓' : '✗';
lines.push(prefix + connector + `${nodeType} ${node.name} (${delegateFlag}) - ${node.scopes.length} scopes`);
const extension = isLast ? ' ' : '│ ';
const newPrefix = prefix + extension;
for (let i = 0; i < node.children.length; i++) {
this.buildAsciiTree(node.children[i], lines, newPrefix, i === node.children.length - 1);
}
}
static buildMermaidNodes(node, nodeDefinitions, connections, parentId) {
const nodeId = this.sanitizeId(node.id);
const label = `${node.name}<br/>${node.scopes.length} scopes`;
nodeDefinitions.push(`${nodeId}["${label}"]`);
if (parentId) {
const edgeLabel = node.canDelegate ? 'can delegate' : 'cannot delegate';
connections.push(`${parentId} -->|"${edgeLabel}"| ${nodeId}`);
}
for (const child of node.children) {
this.buildMermaidNodes(child, nodeDefinitions, connections, nodeId);
}
}
static sanitizeId(id) {
return id.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 20);
}
static findNodesByType(node, type) {
const nodes = [];
if (node.type === type) {
nodes.push(node);
}
for (const child of node.children) {
nodes.push(...this.findNodesByType(child, type));
}
return nodes;
}
static findNodesByCondition(node, condition) {
const nodes = [];
if (condition(node)) {
nodes.push(node);
}
for (const child of node.children) {
nodes.push(...this.findNodesByCondition(child, condition));
}
return nodes;
}
static nodeToD3Json(node) {
return {
name: node.name,
value: node.scopes.length,
data: {
id: node.id,
type: node.type,
canDelegate: node.canDelegate,
scopes: node.scopes
},
children: node.children.map(child => this.nodeToD3Json(child))
};
}
}
exports.DelegationChainVisualizer = DelegationChainVisualizer;
//# sourceMappingURL=delegation-chain-visualizer.js.map