initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-12-27 03:39:14 +01:00
commit 74d5e0a94c
Se han modificado 37 ficheros con 6509 adiciones y 0 borrados

326
docs/MODULE_DEVELOPMENT.md Archivo normal
Ver fichero

@@ -0,0 +1,326 @@
# Module Development Guide
## Introduction
Modules are the primary way to extend the Prosody Node.js server. They can add new features, handle stanzas, store data, and integrate with external services.
## Module Structure
A basic module consists of a JavaScript file that exports an object with specific properties:
```javascript
module.exports = {
name: 'example',
version: '1.0.0',
description: 'An example module',
author: 'Your Name',
dependencies: [], // Other modules this depends on
load(module) {
// Called when module is loaded
},
unload(module) {
// Called when module is unloaded
}
};
```
## Module API
The `module` object passed to `load()` provides access to the server API:
### Properties
- `module.name` - Module name
- `module.host` - Virtual host this module is loaded for
- `module.api` - API object with helper functions
### API Object
```javascript
module.api = {
config, // Configuration manager
hostManager, // Host manager
sessionManager, // Session manager
stanzaRouter, // Stanza router
storageManager, // Storage manager
events, // Event emitter
logger, // Logger instance
// Helper methods
getConfig(key, defaultValue),
getGlobalConfig(key, defaultValue),
getHostConfig(key, defaultValue),
require(moduleName) // Load another module
};
```
## Examples
### 1. Simple Echo Module
```javascript
// modules/mod_echo.js
module.exports = {
name: 'echo',
version: '1.0.0',
description: 'Echoes messages back to sender',
load(module) {
const { logger, stanzaRouter } = module.api;
logger.info('Echo module loaded');
module.hook('message', (stanza, session) => {
if (stanza.attrs.type === 'chat') {
const body = stanza.getChildText('body');
if (body) {
// Create echo response
const response = stanza.clone();
response.attrs.from = stanza.attrs.to;
response.attrs.to = stanza.attrs.from;
session.send(response);
logger.debug(`Echoed message to ${stanza.attrs.from}`);
}
}
});
}
};
```
### 2. Message Logger Module
```javascript
// modules/mod_message_logger.js
module.exports = {
name: 'message_logger',
version: '1.0.0',
description: 'Logs all messages to storage',
async load(module) {
const { logger, stanzaRouter, storageManager } = module.api;
const store = storageManager.getStore(module.host, 'message_log');
logger.info('Message logger module loaded');
stanzaRouter.on('message', async (stanza, session) => {
try {
const logEntry = {
timestamp: Date.now(),
from: stanza.attrs.from,
to: stanza.attrs.to,
type: stanza.attrs.type,
body: stanza.getChildText('body')
};
const key = `msg_${Date.now()}_${Math.random()}`;
await store.set(key, logEntry);
logger.debug('Message logged');
} catch (error) {
logger.error('Failed to log message:', error);
}
});
}
};
```
### 3. User Statistics Module
```javascript
// modules/mod_user_stats.js
module.exports = {
name: 'user_stats',
version: '1.0.0',
description: 'Tracks user statistics',
load(module) {
const { logger, sessionManager, storageManager } = module.api;
const store = storageManager.getStore(module.host, 'user_stats');
const stats = {
totalMessages: 0,
totalPresence: 0,
onlineUsers: new Set()
};
// Track sessions
sessionManager.on('session:authenticated', (session) => {
stats.onlineUsers.add(session.bareJid);
logger.info(`User online: ${session.bareJid} (total: ${stats.onlineUsers.size})`);
});
sessionManager.on('session:closed', (session) => {
stats.onlineUsers.delete(session.bareJid);
logger.info(`User offline: ${session.bareJid} (total: ${stats.onlineUsers.size})`);
});
// Track stanzas
module.on('message', () => {
stats.totalMessages++;
});
module.on('presence', () => {
stats.totalPresence++;
});
// Expose stats via IQ
module.hook('iq', (stanza, session) => {
if (stanza.attrs.type === 'get') {
const query = stanza.getChild('query');
if (query && query.attrs.xmlns === 'http://example.com/stats') {
const response = new ltx.Element('iq', {
type: 'result',
id: stanza.attrs.id,
to: stanza.attrs.from
}).c('query', { xmlns: 'http://example.com/stats' })
.c('stat', { name: 'messages' }).t(stats.totalMessages.toString()).up()
.c('stat', { name: 'presence' }).t(stats.totalPresence.toString()).up()
.c('stat', { name: 'online' }).t(stats.onlineUsers.size.toString());
session.send(response);
return true; // Handled
}
}
});
}
};
```
### 4. Rate Limiting Module
```javascript
// modules/mod_rate_limit.js
const { RateLimiterMemory } = require('rate-limiter-flexible');
module.exports = {
name: 'rate_limit',
version: '1.0.0',
description: 'Rate limits stanzas per user',
load(module) {
const { logger, stanzaRouter, config } = module.api;
const limiter = new RateLimiterMemory({
points: module.api.getConfig('points', 10),
duration: module.api.getConfig('duration', 1)
});
// Add filter to stanza router
stanzaRouter.addFilter(async (stanza, session) => {
if (!session || !session.bareJid) return true;
try {
await limiter.consume(session.bareJid);
return true; // Allow stanza
} catch (error) {
logger.warn(`Rate limit exceeded for ${session.bareJid}`);
return false; // Block stanza
}
});
logger.info('Rate limiting enabled');
}
};
```
## Hooks and Events
### Stanza Events
- `message` - Message stanza received
- `presence` - Presence stanza received
- `iq` - IQ stanza received
- `iq:get` - IQ get request
- `iq:set` - IQ set request
- `iq:result` - IQ result
- `iq:error` - IQ error
### Session Events
- `session:created` - New session created
- `session:authenticated` - Session authenticated
- `session:closed` - Session closed
### Server Events
- `server:started` - Server started
- `server:stopped` - Server stopped
## Storage
Modules can store persistent data:
```javascript
const store = storageManager.getStore(module.host, 'mydata');
// Store data
await store.set('key', { data: 'value' });
// Retrieve data
const data = await store.get('key');
// Delete data
await store.delete('key');
// Find data
const results = await store.find(item => item.type === 'important');
```
## Configuration
Modules can access configuration:
```javascript
// Module-specific config from moduleConfig section
const enabled = module.api.getConfig('enabled', true);
// Global config
const domain = module.api.getGlobalConfig('server.domain');
// Host-specific config
const modules = module.api.getHostConfig('modules', []);
```
## Best Practices
1. **Error Handling**: Always use try-catch blocks
2. **Logging**: Log important events and errors
3. **Cleanup**: Implement proper cleanup in `unload()`
4. **Performance**: Avoid blocking operations
5. **Testing**: Write tests for your modules
6. **Documentation**: Document module configuration
## Testing
```javascript
// test/mod_example.test.js
const ModuleManager = require('../src/core/module-manager');
describe('Example Module', () => {
it('should load successfully', async () => {
// Test module loading
});
it('should handle messages', async () => {
// Test message handling
});
});
```
## Publishing
To share your module:
1. Create a repository
2. Add proper documentation
3. Include example configuration
4. Publish to npm (optional)
## Resources
- [XMPP RFCs](https://xmpp.org/rfcs/)
- [XMPP Extensions (XEPs)](https://xmpp.org/extensions/)
- [Module Examples](examples/modules/)