Files
p2ns/plugin-sites/example.plugin/index.js
T
Raven Scott 6ee20a3f68 Migrate plugin channels to protomux-rpc only
Replace dual message-channel + RPC setup with a single registerPluginProtocol
path, unified sdk.channels (register/request/event/broadcast), RPC keepalive
(__p2ns.ping/pong), and core request lifecycle on RPC open. Update
global.profile and example.plugin, admin Plugin RPC stats, docs, and
test:plugin-rpc. Breaking: upgrade all peers together; no legacy adapters.
2026-05-28 11:55:01 -04:00

353 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Example Plugin
*
* This is a comprehensive template plugin for P2NS.
* Use this as a starting point when creating new plugins.
*
* This plugin demonstrates:
* - Basic request handling
* - Static file serving from www/ directory
* - Simple API endpoint
* - Plugin lifecycle hooks
* - Admin settings integration
* - Admin actions integration
* - Using settings in handler and frontend
*/
const sdk = require('../../includes/plugins/sdk');
// Store action results for display
let lastActionResult = null;
let actionHistory = [];
/**
* Plugin Handler
*
* Handles all HTTP requests for example.plugin
*
* @param {Object} req - Node.js HTTP request object
* @param {Object} res - Node.js HTTP response object
* @returns {Promise<boolean>}
* - true: Request was handled by this plugin
* - false: Request should fall back to static file serving
*/
async function handler(req, res) {
try {
// Parse request using SDK router
// This extracts the path and query parameters from the URL
const { path, query, method } = sdk.router.parseRequest(req);
// Handle root path - return false to serve index.html from www/ directory
if (path === '' || path === '/') {
return false;
}
// Example: Handle a simple API endpoint that uses settings
// GET /api/hello?name=World
if (path === 'api/hello' && method === 'GET') {
// Get the greeting prefix from settings
const greetingPrefix = await sdk.admin.getSetting('exampleString', 'Hello');
const name = query.name || 'World';
// Use the boolean setting to determine if we should include timestamp
const includeTimestamp = await sdk.admin.getSetting('exampleBoolean', false);
const response = {
message: `${greetingPrefix}, ${name}!`,
};
if (includeTimestamp) {
response.timestamp = new Date().toISOString();
}
return sdk.router.json(res, response);
}
// Example: Handle another API endpoint that uses settings
// GET /api/info
if (path === 'api/info' && method === 'GET') {
// Get all settings to show how they're being used
const exampleString = await sdk.admin.getSetting('exampleString', 'Hello, World!');
const exampleNumber = await sdk.admin.getSetting('exampleNumber', 42);
const exampleBoolean = await sdk.admin.getSetting('exampleBoolean', false);
const exampleSelect = await sdk.admin.getSetting('exampleSelect', 'option1');
return sdk.router.json(res, {
plugin: 'example.plugin',
version: '1.0.0',
description: 'A comprehensive template plugin demonstrating admin settings and actions',
dnsReady: sdk.utils.isDNSReady(),
connectedPeers: sdk.state.connectedPeers,
settings: {
exampleString,
exampleNumber,
exampleBoolean,
exampleSelect
},
settingsInUse: {
greetingPrefix: exampleString,
multiplier: exampleNumber,
showTimestamps: exampleBoolean,
mode: exampleSelect
}
});
}
// Example: Get user's public key
// GET /api/public-key
if (path === 'api/public-key' && method === 'GET') {
const publicKey = sdk.state.localPeerId;
return sdk.router.json(res, {
publicKey: publicKey || null,
hasIdentity: !!publicKey
});
}
// Example: API endpoint that uses number setting
// GET /api/calculate?value=10
if (path === 'api/calculate' && method === 'GET') {
const exampleNumber = await sdk.admin.getSetting('exampleNumber', 42);
const inputValue = parseFloat(query.value) || 0;
const result = inputValue * exampleNumber;
return sdk.router.json(res, {
input: inputValue,
multiplier: exampleNumber,
result: result,
calculation: `${inputValue} × ${exampleNumber} = ${result}`
});
}
// Example: API endpoint that uses select setting
// GET /api/mode
if (path === 'api/mode' && method === 'GET') {
const exampleSelect = await sdk.admin.getSetting('exampleSelect', 'option1');
const modeDescriptions = {
option1: 'Standard mode - basic functionality',
option2: 'Enhanced mode - additional features',
option3: 'Advanced mode - full feature set'
};
return sdk.router.json(res, {
mode: exampleSelect,
description: modeDescriptions[exampleSelect] || 'Unknown mode',
availableModes: ['option1', 'option2', 'option3']
});
}
// Example: Get action history
// GET /api/actions
if (path === 'api/actions' && method === 'GET') {
return sdk.router.json(res, {
lastResult: lastActionResult,
history: actionHistory.slice(-10) // Last 10 actions
});
}
// Example: Get all current settings
// GET /api/settings
if (path === 'api/settings' && method === 'GET') {
const allSettings = await sdk.admin.getAllSettings();
return sdk.router.json(res, {
settings: allSettings,
timestamp: new Date().toISOString()
});
}
// Return false for all other routes to allow static file serving
// This means CSS, JS, images, etc. will be served from www/ directory
return false;
} catch (err) {
// Catch any unexpected errors
sdk.log.error('example.plugin', `Error handling request: ${err.message}`);
return sdk.router.error(res, 'Internal Server Error', 500);
}
}
/**
* Plugin Initialization Hook
*
* Called automatically when the plugin is loaded by the P2NS system.
* Use this to initialize resources, set up timers, pre-load data, etc.
*/
async function onInit() {
sdk.log.info('example.plugin', 'Plugin initialized');
sdk.channels.register('rpc-demo', {
autoReconnect: true,
methods: {
'demo.ping': async (value) => ({
pong: true,
echo: value,
at: new Date().toISOString()
})
}
});
sdk.log.info('example.plugin', 'Registered RPC demo.ping on rpc-demo protocol');
// Example: Check if DNS service is ready
if (sdk.utils.isDNSReady()) {
sdk.log.info('example.plugin', 'DNS service is ready');
} else {
sdk.log.warn('example.plugin', 'DNS service is not ready yet');
}
// Register actions that can be called from the admin panel
sdk.admin.registerAction('testAction', async (params) => {
sdk.log.info('example.plugin', `Test action called with params: ${JSON.stringify(params)}`);
// Get current settings to use in action
const exampleString = await sdk.admin.getSetting('exampleString', 'Hello, World!');
const exampleNumber = await sdk.admin.getSetting('exampleNumber', 42);
const exampleBoolean = await sdk.admin.getSetting('exampleBoolean', false);
const exampleSelect = await sdk.admin.getSetting('exampleSelect', 'option1');
const result = {
success: true,
message: 'Test action executed successfully!',
timestamp: new Date().toISOString(),
params: params || {},
settingsUsed: {
greeting: exampleString,
multiplier: exampleNumber,
featureEnabled: exampleBoolean,
mode: exampleSelect
},
calculation: `Using multiplier ${exampleNumber}: ${(params?.value || 10) * exampleNumber}`
};
// Store result for API access
lastActionResult = result;
actionHistory.push({
timestamp: new Date().toISOString(),
action: 'testAction',
params,
result
});
// Keep history limited to last 50 actions
if (actionHistory.length > 50) {
actionHistory = actionHistory.slice(-50);
}
return result;
}, {
label: 'Test Action',
description: 'A test action that uses current settings and demonstrates the action system',
icon: '🧪'
});
// Register a more complex action that demonstrates different capabilities
sdk.admin.registerAction('resetData', async (params) => {
sdk.log.info('example.plugin', 'Reset data action called');
// Clear action history
actionHistory = [];
lastActionResult = null;
return {
success: true,
message: 'Action history and cached data have been reset',
timestamp: new Date().toISOString()
};
}, {
label: 'Reset Data',
description: 'Clear action history and reset cached data',
icon: '🔄'
});
// Register a calculation action that uses the number setting
sdk.admin.registerAction('calculate', async (params) => {
const exampleNumber = await sdk.admin.getSetting('exampleNumber', 42);
const inputValue = params?.value ? parseFloat(params.value) : 10;
const result = inputValue * exampleNumber;
const calcResult = {
success: true,
message: `Calculation completed using multiplier ${exampleNumber}`,
input: inputValue,
multiplier: exampleNumber,
result: result,
calculation: `${inputValue} × ${exampleNumber} = ${result}`,
timestamp: new Date().toISOString()
};
actionHistory.push({
timestamp: new Date().toISOString(),
action: 'calculate',
params,
result: calcResult
});
if (actionHistory.length > 50) {
actionHistory = actionHistory.slice(-50);
}
return calcResult;
}, {
label: 'Calculate',
description: 'Perform a calculation using the number setting as multiplier',
icon: '🔢'
});
// Register settings that will appear in the admin panel
sdk.admin.registerSetting('exampleString', {
type: 'string',
label: 'Greeting Prefix',
description: 'The greeting prefix used in the /api/hello endpoint (e.g., "Hello", "Hi", "Greetings")',
default: 'Hello, World!'
});
sdk.admin.registerSetting('exampleNumber', {
type: 'number',
label: 'Multiplier',
description: 'A number used as a multiplier in calculations (used in /api/calculate and actions)',
default: 42,
min: 1,
max: 1000
});
sdk.admin.registerSetting('exampleBoolean', {
type: 'boolean',
label: 'Include Timestamps',
description: 'When enabled, API responses will include timestamp fields',
default: false
});
sdk.admin.registerSetting('exampleSelect', {
type: 'select',
label: 'Operation Mode',
description: 'Select the operational mode for the plugin',
default: 'option1',
options: [
{ value: 'option1', label: 'Standard Mode' },
{ value: 'option2', label: 'Enhanced Mode' },
{ value: 'option3', label: 'Advanced Mode' }
]
});
// Log initial settings
const initialSettings = await sdk.admin.getAllSettings();
sdk.log.info('example.plugin', `Initial settings loaded: ${JSON.stringify(initialSettings)}`);
}
/**
* Plugin Shutdown Hook
*
* Called automatically when the plugin is unloaded by the P2NS system.
* Use this to clean up resources, close connections, stop timers, etc.
*/
async function onShutdown() {
sdk.log.info('example.plugin', 'Plugin shutting down');
// Add cleanup code here if needed
}
// Export the plugin interface
module.exports = {
handler, // HTTP request handler - processes all incoming requests
onInit, // Called when plugin loads
onShutdown // Called when plugin unloads
};