P2NS Plugin System
The P2NS plugin system allows you to create custom internal websites. Each internal domain is automatically discovered from plugins in plugin-sites/{domain}/ that have a valid config.json file. The system will automatically register any plugin with a valid config.json as an internal domain.
Note: p2ns.admin is always treated as an internal domain, even without a plugin.
Plugin Documentation Index
This directory contains comprehensive documentation for the P2NS plugin system:
Core Documentation
- README.md - This file: Complete plugin system guide
- PLUGIN_SDK.md - Complete Plugin SDK API reference
SDK Features
- HYPERDB.md - HyperDB database integration for plugins
- HYPERDRIVE.md - Hyperdrive distributed file system for plugins
- PLUGIN_CHANNELS.md - P2P communication channels via Protomux
Example Plugins
- example.plugin.md - Example Plugin template documentation
- global.profile.md - Global Profile plugin documentation
- domain.consensus.md - Domain Consensus plugin documentation
- peer.directory.md - Peer Directory plugin documentation
- peer.visualize.md - Peer Visualize plugin documentation
- file.drop.md - File Drop plugin documentation
- peer.paste.md - Peer Paste plugin documentation
- vis-network-migration.md - Migration guide for visualization libraries
Related Documentation
- ../RESTAPI.md - REST API endpoints including plugin management
- ../EXAMPLES.md - API usage examples
Quick Start
-
Create a plugin directory structure:
mkdir -p plugin-sites/my.custom.domain/www -
Create
config.jsonin your plugin directory:{ "name": "my.custom.domain", "version": "1.0.0", "description": "My custom plugin", "author": "Your Name", "domain": "my.custom.domain", "homepage": "https://example.com", "license": "MIT", "dependencies": {}, "www": "www" } -
Create
index.js(optional - only if you need dynamic handling):const sdk = require('../../includes/plugins/sdk'); module.exports = { async handler(req, res) { const { path } = sdk.router.parseRequest(req); // Handle API endpoint if (path === 'api/hello') { return sdk.router.json(res, { message: 'Hello!' }); } // Return false to fall back to static file serving return false; }, async onInit() { sdk.log.info('my.custom.domain', 'Plugin initialized'); }, async onShutdown() { sdk.log.info('my.custom.domain', 'Plugin shutting down'); } }; -
Add static files to
www/directory (HTML, CSS, JS, images, etc.) -
Restart P2NS - your plugin will be automatically loaded!
Plugin Structure
Plugins use a document root structure with a www/ directory for static files and optional index.js for dynamic handling.
Directory Structure
plugin-sites/
{domain}/
├── config.json # Plugin metadata (required)
├── index.js # Plugin handler (optional)
└── www/ # Document root for static files
├── index.html
├── css/
├── js/
└── assets/
config.json
The config.json file contains plugin metadata:
{
"name": "plugin-name",
"version": "1.0.0",
"description": "Plugin description",
"author": "Author name",
"homepage": "https://example.com",
"license": "MIT",
"dependencies": {},
"www": "www"
}
Required fields:
name- Plugin name (defaults to directory name)version- Plugin version (defaults to "1.0.0")
Optional fields:
description- Plugin descriptionauthor- Author namehomepage- Homepage URLlicense- License identifierdependencies- Dependency object (for future use)www- Document root directory name (defaults to "www")
Plugin Handler (index.js)
The index.js file is optional. If present, it must export an object with the following interface:
Optional:
-
handler(req, res)- Function that handles HTTP requestsreq- Node.js HTTP request objectres- Node.js HTTP response object- Return
falseto fall back to static file serving fromwww/ - Return
trueor nothing if request was handled
-
onInit()- Called when the plugin is loaded- Use this to initialize resources, set up timers, etc.
-
onShutdown()- Called when the plugin is unloaded- Use this to clean up resources, close connections, etc.
Note: If no index.js exists but www/ directory exists, the plugin will serve static files only.
Router Utilities
The SDK provides router utilities to simplify request handling:
const sdk = require('../../includes/plugins/sdk');
module.exports = {
async handler(req, res) {
// Parse request to get path and query parameters
const { path, query } = sdk.router.parseRequest(req);
// Handle API endpoint
if (path === 'api/data') {
const data = { message: 'Hello from plugin' };
return sdk.router.json(res, data);
}
// Handle text response
if (path === 'status') {
return sdk.router.text(res, 'OK', 200);
}
// Handle errors
if (path === 'error') {
return sdk.router.error(res, 'Something went wrong', 500);
}
// Handle 404
if (path === 'missing') {
return sdk.router.notFound(res, 'Resource not found');
}
// Return false to fall back to static file serving
return false;
}
};
Router Methods:
sdk.router.parseRequest(req)- Parse request to extract{ path, query, method }- Automatically handles domain prefixes (e.g.,
/peer.directory/api→api)
- Automatically handles domain prefixes (e.g.,
sdk.router.json(res, data, status = 200)- Send JSON responsesdk.router.text(res, text, status = 200, contentType = 'text/plain')- Send text responsesdk.router.error(res, message, status = 500)- Send error response as JSONsdk.router.notFound(res, message = 'Not Found')- Send 404 response
Plugin SDK
The SDK provides access to P2NS system functionality:
State Access
// Get connected peers count
const peerCount = sdk.state.connectedPeers;
// Get list of peer IDs
const peerIds = sdk.state.peerIds;
// Get local peer ID (Hyperswarm public key)
const localPeerId = sdk.state.localPeerId;
// Get dnsPass instance (may be null if not initialized)
const dnsPass = sdk.state.dnsPass;
// Get domain to IP mapping
const ipMap = sdk.state.domainToIPMap;
// Get IP for a specific domain
const ip = sdk.state.getIPForDomain('example.tld');
// Get all active Holesail connections
const holesails = sdk.state.holesails;
// Get peer channels
const peerChannels = sdk.state.peerChannels;
// Get peer metrics
const peerMetrics = sdk.state.peerMetrics;
// Get peer history
const peerHistory = sdk.state.peerHistory;
Plugin Information
// Get plugin configuration
const config = sdk.plugin.getConfig();
// Get specific config value
const version = sdk.plugin.getConfigValue('version');
// Get all domains that have plugins loaded
const pluginDomains = sdk.plugin.getAllPluginDomains();
// Get www directory path
const wwwDir = sdk.plugin.getWwwDir();
// Get plugin directory path
const pluginDir = sdk.plugin.getPluginDir();
DNS Operations
// Get hash for a domain
const hash = await sdk.dns.getHashForDomain('example.tld');
// Get all DNS entries
const entries = await sdk.dns.getAllEntries();
// Get consensus state for a domain
const consensus = await sdk.dns.getConsensusState('example.tld');
// Get consensus metrics
const metrics = sdk.dns.getConsensusMetrics();
Logging
sdk.log.debug('my-plugin', 'Debug message');
sdk.log.info('my-plugin', 'Info message');
sdk.log.warn('my-plugin', 'Warning message');
sdk.log.error('my-plugin', 'Error message');
Note: Plugin logs are automatically broadcast to the admin interface and displayed in real-time in the plugin's log terminal. Logs are also written to app.log in the plugin directory.
Core Operations
// Get all entries
const entries = await sdk.core.getAllEntries();
// Get hash for domain
const hash = await sdk.core.getHashForDomain('example.tld');
// Get consensus state
const state = await sdk.core.getConsensusState('example.tld');
Domain Management
// Add a new domain
await sdk.domains.addDomain('example.tld', 'hs://hash123', [
{ name: 'web', port: 80, protocol: 'tcp' }
]);
// Remove a domain
await sdk.domains.removeDomain('example.tld');
// Vote for a specific claimant
await sdk.domains.voteForDomain('example.tld', 'claimant-id');
// Get complete domain information
const info = await sdk.domains.getDomainInfo('example.tld');
// List all domains
const domains = await sdk.domains.listDomains();
// Get domain IP
const ip = sdk.domains.getDomainIP('example.tld');
// Create interface for domain
const ip = await sdk.domains.createInterfaceForDomain('example.tld');
Holesail Operations
// Create a Holesail server
const server = await sdk.holesail.createServer({
name: 'my-server',
port: 8080,
host: '0.0.0.0',
secure: true,
udp: false
});
// List all servers
const servers = sdk.holesail.listServers();
// Get server info
const server = sdk.holesail.getServer('server-id');
// Restart a server
await sdk.holesail.restartServer('server-id');
// Stop a server
await sdk.holesail.stopServer('server-id');
// Remove a server
await sdk.holesail.removeServer('server-id');
// Create a Holesail client
const client = await sdk.holesail.createClient({
domain: 'example.tld',
key: 'hs://hash123',
port: 8080,
protocol: 'tcp'
});
// List all clients
const clients = sdk.holesail.listClients();
// Get client info
const client = sdk.holesail.getClient('client-id');
// Restart a client
await sdk.holesail.restartClient('client-id');
// Stop a client
await sdk.holesail.stopClient('client-id');
// Remove a client
await sdk.holesail.removeClient('client-id');
Peer Management
// Get peer information
const peerInfo = sdk.peers.getPeerInfo('peer-id');
// Get peer metrics
const metrics = sdk.peers.getPeerMetrics('peer-id');
// Get peer history
const history = sdk.peers.getPeerHistory('peer-id');
// Block a peer
await sdk.peers.blockPeer('peer-id');
// Unblock a peer
await sdk.peers.unblockPeer('peer-id');
// Check if peer is blocked
const isBlocked = sdk.peers.isPeerBlocked('peer-id');
// List all blocked peers
const blocked = sdk.peers.getBlockedPeers();
Certificate Management
// Get certificate for domain
const cert = await sdk.certificates.getCertificate('example.tld');
// Create certificate for domain
const cert = sdk.certificates.createCertificate('example.tld', [
{ type: 2, value: 'example.tld' },
{ type: 7, value: '127.0.0.1' }
]);
// List all certificates
const certs = await sdk.certificates.listCertificates();
// Get root CA info
const rootCA = sdk.certificates.getRootCA();
Local DNS Management
// Add a local DNS record
await sdk.localDns.addRecord({
name: 'example.tld',
type: 'A',
class: 'IN',
ttl: 3600,
address: '192.168.1.1'
});
// Remove DNS record(s)
await sdk.localDns.removeRecord('example.tld', 'A');
// List all records
const records = sdk.localDns.listRecords();
// Get specific record(s)
const records = sdk.localDns.getRecord('example.tld', 'A');
// Update a record
await sdk.localDns.updateRecord(0, {
name: 'example.tld',
type: 'A',
class: 'IN',
ttl: 7200,
address: '192.168.1.2'
});
Interface Management
// List all interfaces
const interfaces = sdk.interfaces.listInterfaces();
// Get interface for domain
const iface = sdk.interfaces.getInterfaceForDomain('example.tld');
// Create interface for domain
const ip = await sdk.interfaces.createInterface('example.tld');
// Remove interface for domain
await sdk.interfaces.removeInterface('example.tld');
Metrics and Monitoring
// Get system-wide metrics
const metrics = sdk.metrics.getSystemMetrics();
// Get peer metrics
const peerMetrics = sdk.metrics.getPeerMetrics();
// Get domain metrics
const domainMetrics = sdk.metrics.getDomainMetrics('example.tld');
// Get Holesail metrics
const holesailMetrics = sdk.metrics.getHolesailMetrics();
// Get consensus metrics
const consensusMetrics = sdk.metrics.getConsensusMetrics();
// Get process metrics
const processMetrics = await sdk.metrics.getProcessMetrics();
// Get historical data
const historical = sdk.metrics.getHistoricalData('requests', 100);
WebSocket Support
// Broadcast message to all WebSocket clients
sdk.websocket.broadcast({ type: 'update', data: 'something changed' });
// Get WebSocket client count
const count = sdk.websocket.getClientCount();
Configuration Access
// Get all configuration
const config = sdk.config.getConfig();
// Get specific config value
const port = sdk.config.getConfigValue('INTERNAL_PORT');
// Get environment variable
const nodeEnv = sdk.config.getEnvironmentVariable('NODE_ENV');
// Validate configuration
const validated = sdk.config.validateConfig();
Backup and Restore
// Create a manual backup
const backupPath = await sdk.backup.createBackup();
// List all backups
const backups = await sdk.backup.listBackups();
// Restore from backup
await sdk.backup.restoreBackup('backup-2025-12-02.tar.gz');
// Get backup information
const info = await sdk.backup.getBackupInfo('backup-2025-12-02.tar.gz');
Subscription Management
// Add subscription
await sdk.subscriptions.addSubscription(
'example.tld',
'web',
'hs://hash123',
80,
'tcp'
);
// Remove subscription
await sdk.subscriptions.removeSubscription('example.tld', 'web');
// List all subscriptions
const subscriptions = await sdk.subscriptions.listSubscriptions();
// Get subscriptions for domain
const subs = await sdk.subscriptions.getSubscriptionsForDomain('example.tld');
HTTP Client Utilities
// Make HTTP request
const response = await sdk.http.request('https://api.example.com/data', {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
});
// GET request
const response = await sdk.http.get('https://api.example.com/data');
// POST request
const response = await sdk.http.post('https://api.example.com/data', {
key: 'value'
});
// PUT request
const response = await sdk.http.put('https://api.example.com/data', {
key: 'value'
});
// DELETE request
const response = await sdk.http.delete('https://api.example.com/data');
File System Utilities
// Get plugin directory
const pluginDir = sdk.fs.getPluginDir();
// Read file (within plugin directory)
const content = await sdk.fs.readFile('data.json');
// Write file (within plugin directory)
await sdk.fs.writeFile('data.json', JSON.stringify({ key: 'value' }));
// List directory
const files = await sdk.fs.readDir('assets');
// Check if file exists
const exists = await sdk.fs.exists('config.json');
Event System
// Subscribe to system events
sdk.events.on('domain-added', (data) => {
console.log('Domain added:', data.domain);
});
// Subscribe once
sdk.events.once('peer-connected', (data) => {
console.log('Peer connected:', data.peerId);
});
// Unsubscribe
sdk.events.off('domain-added', handler);
// Emit custom event
sdk.events.emit('custom-event', { data: 'value' });
Enhanced Utilities
// Check if DNS service is initialized
const isReady = sdk.utils.isDNSReady();
// Get internal domains list
const domains = await sdk.utils.getInternalDomains();
// Check if a domain is internal
const isInternal = await sdk.utils.isInternalDomain('example.tld');
// Wait for DNS to be ready
await sdk.utils.waitForDNSReady(30000);
// Wait for peer to connect
await sdk.utils.waitForPeer('peer-id', 30000);
// Retry with exponential backoff
const result = await sdk.utils.retry(async () => {
return await someOperation();
}, { maxRetries: 3, initialDelay: 1000 });
// Debounce function
const debouncedFn = sdk.utils.debounce(() => {
console.log('Called after delay');
}, 1000);
// Throttle function
const throttledFn = sdk.utils.throttle(() => {
console.log('Called at most once per delay');
}, 1000);
// Sleep/delay
await sdk.utils.sleep(5000); // Wait 5 seconds
// Format Holesail hash
const formatted = sdk.utils.formatHash('hash123'); // Returns 'hs://hash123'
// Parse Holesail hash
const parsed = sdk.utils.parseHash('hs://hash123'); // Returns 'hash123'
Security and Validation
// Validate domain name
if (sdk.security.validateDomain('example.tld')) {
// Domain is valid
}
// Validate Holesail hash
if (sdk.security.validateHash('hs://hash123')) {
// Hash is valid
}
// Sanitize user input
const safe = sdk.security.sanitizeInput(userInput);
// Check permission
if (sdk.security.checkPermission('add-domain')) {
// Operation allowed
}
Request Handling Order
The system handles requests in the following order:
- Static files from
www/directory - Files are served directly from thewww/directory - Plugin handler - If a static file is not found, the plugin's
handler()function is called - 404 Not Found - If neither static files nor handler handle the request
This means you can:
- Serve static HTML/CSS/JS from
www/without any handler code - Use a handler for dynamic routes while serving static assets from
www/ - Return
falsefrom handler to fall back to static file serving
Static File Serving
Static files are automatically served from the www/ directory. Just place your HTML, CSS, JS, and other assets in the www/ subdirectory.
Example structure:
plugin-sites/my.custom.domain/
├── config.json
├── index.js # Optional handler
└── www/ # Document root
├── index.html
├── styles.css
├── app.js
└── images/
└── logo.png
Files in www/ are served with appropriate MIME types based on file extensions.
Examples
Simple HTML Response
const sdk = require('../../includes/plugins/sdk');
module.exports = {
async handler(req, res) {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Hello World!</h1>');
} else {
res.writeHead(404);
res.end('Not Found');
}
}
};
Full-Featured P2P Site
const sdk = require('../../includes/plugins/sdk');
module.exports = {
async handler(req, res) {
const url = new URL(req.url, `https://${req.headers.host}`);
// API endpoint to list domains
if (url.pathname === '/api/domains' && req.method === 'GET') {
const domains = await sdk.domains.listDomains();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ domains }));
return;
}
// API endpoint to add domain
if (url.pathname === '/api/domains' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', async () => {
try {
const { domain, hash } = JSON.parse(body);
await sdk.domains.addDomain(domain, hash);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: err.message }));
}
});
return;
}
// API endpoint to get peer info
if (url.pathname.startsWith('/api/peers/') && req.method === 'GET') {
const peerId = url.pathname.split('/api/peers/')[1];
const peerInfo = sdk.peers.getPeerInfo(peerId);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(peerInfo));
return;
}
// Serve HTML page
if (url.pathname === '/') {
const domains = await sdk.domains.listDomains();
const peers = sdk.state.peerIds.map(id => sdk.peers.getPeerInfo(id));
const metrics = sdk.metrics.getSystemMetrics();
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head><title>P2P Site</title></head>
<body>
<h1>P2P Site Dashboard</h1>
<h2>Domains: ${domains.length}</h2>
<h2>Peers: ${peers.length}</h2>
<h2>Metrics</h2>
<pre>${JSON.stringify(metrics, null, 2)}</pre>
</body>
</html>
`);
return;
}
res.writeHead(404);
res.end('Not Found');
},
async onInit() {
sdk.log.info('my-plugin', 'Initializing plugin...');
// Subscribe to events
sdk.events.on('domain-added', (data) => {
sdk.log.info('my-plugin', `Domain added: ${data.domain}`);
sdk.websocket.broadcast({ type: 'domain-added', domain: data.domain });
});
// Wait for DNS to be ready
await sdk.utils.waitForDNSReady();
sdk.log.info('my-plugin', 'DNS service is ready');
},
async onShutdown() {
sdk.log.info('my-plugin', 'Shutting down plugin...');
// Cleanup resources
}
};
REST API with Full SDK
const sdk = require('../../includes/plugins/sdk');
module.exports = {
async handler(req, res) {
const url = new URL(req.url, `https://${req.headers.host}`);
// Domains API
if (url.pathname === '/api/domains' && req.method === 'GET') {
const domains = await sdk.domains.listDomains();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ domains }));
return;
}
if (url.pathname === '/api/domains' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', async () => {
try {
const { domain, hash, clients } = JSON.parse(body);
await sdk.domains.addDomain(domain, hash, clients);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: err.message }));
}
});
return;
}
// Holesail API
if (url.pathname === '/api/holesail/servers' && req.method === 'GET') {
const servers = sdk.holesail.listServers();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ servers }));
return;
}
if (url.pathname === '/api/holesail/servers' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', async () => {
try {
const opts = JSON.parse(body);
const server = await sdk.holesail.createServer(opts);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(server));
} catch (err) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: err.message }));
}
});
return;
}
// Metrics API
if (url.pathname === '/api/metrics' && req.method === 'GET') {
const metrics = sdk.metrics.getSystemMetrics();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(metrics));
return;
}
res.writeHead(404);
res.end('Not Found');
}
};
Using Existing Handlers
You can also delegate to existing handlers if needed (e.g., for admin interface):
const path = require('path');
const { handleAdminRequest } = require(path.join(__dirname, '../../includes/admin'));
module.exports = {
async handler(req, res) {
// Delegate to existing handler
await handleAdminRequest(req, res);
}
};
Note: The old directory.js handler has been removed. The peer.directory functionality is now provided by the peer.directory plugin in plugin-sites/peer.directory/.
Complete SDK Reference
The SDK provides comprehensive access to all P2NS functionality:
sdk.state- System state access (read-only)sdk.dns- DNS operations and domain resolutionsdk.domains- Domain management (add, remove, vote, list)sdk.holesail- Holesail server and client managementsdk.peers- Peer management and blockingsdk.certificates- TLS certificate managementsdk.localDns- Local DNS record managementsdk.interfaces- Virtual network interface managementsdk.metrics- System metrics and monitoringsdk.websocket- WebSocket broadcastingsdk.config- Configuration accesssdk.backup- Backup and restore operationssdk.subscriptions- Service subscription managementsdk.http- HTTP client utilitiessdk.fs- File system utilities (plugin directory only)sdk.events- Event system for real-time updatessdk.utils- Enhanced utility functionssdk.security- Security and validation utilitiessdk.log- Logging utilitiessdk.core- Core P2NS operationssdk.plugin- Plugin information and configuration accesssdk.router- HTTP request parsing and response helperssdk.admin- Admin panel registration (actions, settings)sdk.db- HyperDB database operations (see HYPERDB.md)sdk.channels- P2P communication channels (see PLUGIN_CHANNELS.md)sdk.drives- Hyperdrive distributed file system (see HYPERDRIVE.md)
Admin Panel Registration
Plugins can register actions and settings that appear in the admin interface:
async onInit() {
// Register an action that can be called from the admin panel
sdk.admin.registerAction('reset-data', async (params) => {
// Action handler
sdk.log.info('my-plugin', 'Resetting data...');
// Perform action
return { success: true, message: 'Data reset' };
}, {
label: 'Reset Data',
description: 'Reset all plugin data to defaults',
icon: '🔄'
});
// Register a setting
sdk.admin.registerSetting('maxItems', {
type: 'number',
label: 'Maximum Items',
description: 'Maximum number of items to display',
default: 100,
min: 1,
max: 1000
});
// Register a boolean setting
sdk.admin.registerSetting('enableFeature', {
type: 'boolean',
label: 'Enable Feature',
description: 'Enable the new feature',
default: false
});
// Register a select setting
sdk.admin.registerSetting('theme', {
type: 'select',
label: 'Theme',
description: 'Choose a theme',
default: 'light',
options: [
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'auto', label: 'Auto' }
]
});
// Access saved settings
const maxItems = await sdk.admin.getSetting('maxItems', 100);
const allSettings = await sdk.admin.getAllSettings();
}
// Access settings in your handler
async handler(req, res) {
const maxItems = await sdk.admin.getSetting('maxItems', 100);
// Use setting value
}
Action Types:
- Actions are async functions that can be called from the admin panel
- They receive a
paramsobject with any parameters - They should return a result object or throw an error
Setting Types:
string- Text inputnumber- Number input (supportsmin,max)boolean- Checkboxselect- Dropdown (requiresoptionsarray)textarea- Multi-line text input
Settings Persistence:
- Settings are automatically saved to
cache/plugin-settings/{domain}.json - Settings persist across plugin restarts
- Use
sdk.admin.getSetting(key, defaultValue)to retrieve saved values - Use
sdk.admin.getAllSettings()to get all saved settings
Best Practices
- Error Handling: Always wrap async operations in try-catch blocks
- Logging: Use the SDK logging functions instead of console.log (logs appear in admin interface)
- Resource Cleanup: Implement
onShutdown()to clean up resources - Security: Validate and sanitize user input using
sdk.security - Performance: Cache expensive operations when possible
- DNS Ready: Check
sdk.utils.isDNSReady()before DNS operations - Events: Use
sdk.eventsfor real-time updates instead of polling - Retry Logic: Use
sdk.utils.retry()for unreliable operations - File Safety: Only use
sdk.fsfor file operations (restricted to plugin directory) - Validation: Always validate user input with
sdk.security.validateDomain()etc. - Admin Integration: Register actions and settings in
onInit()for better admin interface integration - Settings: Use
sdk.admin.getSetting()to access saved settings in your handler
Plugin Management
Plugins can be managed via the admin interface at https://p2ns.admin in the Plugins tab:
- View Plugins: See all installed plugins with their status, version, description, and features
- Start/Stop: Start or stop plugins without restarting P2NS
- Restart: Reload a plugin to apply code changes without restarting P2NS
- Actions: Execute registered plugin actions directly from the admin interface
- Settings: Configure plugin settings with a user-friendly form interface
- Logs: View real-time logs for each plugin in an integrated terminal
Plugin Status:
- Loaded: Plugin is loaded and running
- Stopped: Plugin is stopped and unloaded from memory
- Static: Plugin only serves static files (no handler)
Logs:
- Plugin logs are displayed in real-time in the plugin's log terminal
- Logs are automatically captured from
sdk.logcalls - Logs are also written to
app.login the plugin directory - Log terminals are hidden by default and appear when you start/stop/restart a plugin
Troubleshooting
- Plugin not loading: Check that
config.jsonexists with validnameandversionfields. The plugin will be automatically discovered if it has a valid config.json. - Module not found: Ensure your
index.jsuses correct relative paths - SDK errors: Make sure DNS service is initialized before using DNS operations
- Static files not serving: Ensure files are in the
www/directory and have correct permissions - Config not accessible: Plugin config is only available when handling requests (via
sdk.plugin.getConfig()) - Logs not showing: Ensure you're using
sdk.logmethods instead ofconsole.log. Logs appear in the admin interface after starting/stopping/restarting a plugin. - Settings not persisting: Settings are saved to
cache/plugin-settings/{domain}.json. Ensure the cache directory is writable. - Actions not appearing: Actions must be registered in
onInit(). Ensure the plugin is loaded andonInit()completed successfully.
Migration from Existing Handlers
If you want to migrate an existing internal site (like peer.directory) to use the plugin system:
- Copy the handler code to
plugin-sites/{domain}/index.js - Adapt it to use the plugin SDK
- The system will automatically use your plugin instead of the default handler
The handleAdminRequest handler remains available for the admin interface. The old directory.js handler has been removed and replaced by the peer.directory plugin.