API: Hook Manager - Drowbe/coffee-pub-blacksmith GitHub Wiki
Blacksmith Hook Manager API
For External Module Developers
This document covers the Hook Manager API for registering and managing FoundryVTT hooks through Coffee Pub Blacksmith. For general integration setup, see
api-core.md.
Audience: Developers integrating with Blacksmith and using the Hook Manager for event handling.
Overview
The Hook Manager provides centralized hook management with:
- Priority-based execution (1=Critical, 2=High, 3=Normal, 4=Low, 5=Lowest)
- Context-based cleanup for batch operations
- Throttle/debounce support for performance optimization
- Dedupe protection to prevent duplicate registrations
- Automatic cleanup for "once" hooks
Access
// Direct access - no await needed once Blacksmith is ready
const hookManager = BlacksmithHookManager;
BlacksmithHookManager.registerHook(...)
Core API
Register a Hook
const callbackId = BlacksmithHookManager.registerHook({
name: 'hookName', // Required: FoundryVTT hook name
description: 'Description', // Optional: Human-readable description
context: 'context-name', // Optional: Batch cleanup identifier
priority: 3, // Optional: 1-5, default: 3
key: 'uniqueKey', // Optional: Dedupe protection
options: { // Optional: Performance options
once: true, // Auto-cleanup after first execution
throttleMs: 50, // Max once per 50ms
debounceMs: 300 // Wait 300ms after last call
},
callback: (args) => { /* logic */ } // Required: Your callback function - ALWAYS LAST
});
Parameter Order (Recommended)
For readability and consistency, use this order:
name(required)description(optional)context(optional)priority(optional)key(optional)options(optional)callback(required) - ALWAYS LAST for readability
What We Support
- Required:
name,callback - Optional:
description,priority,options,key,context - Options:
once,throttleMs,debounceMs - Performance: Throttling and debouncing work as documented
- Cleanup:
once: trueauto-removes hooks after first execution - Dedupe:
keyprevents duplicate registrations - Batch cleanup:
contextenables group removal
Other Methods
// Remove a specific callback
const removed = BlacksmithHookManager.removeCallback(callbackId);
// Cleanup by context
BlacksmithHookManager.disposeByContext('context-name');
// Get statistics and debugging info
BlacksmithHookManager.showHooks();
BlacksmithHookManager.showHookDetails();
BlacksmithHookManager.getStats();
Working Examples
Basic Hook Registration
const hookId = BlacksmithHookManager.registerHook({
name: 'updateActor',
description: 'My module: Track actor changes',
context: 'my-module',
priority: 3,
callback: (actor, changes) => {
console.log(`Actor ${actor.name} updated:`, changes);
}
});
Hook with All Parameters
const fullHookId = BlacksmithHookManager.registerHook({
name: 'updateActor',
description: 'My module: Track actor changes',
context: 'my-module',
priority: 3,
key: 'unique-actor-tracker', // Prevents duplicate registrations
options: {
once: false, // Don't auto-cleanup
throttleMs: 100 // Max once per 100ms
},
callback: (actor, changes) => {
console.log(`Actor ${actor.name} updated:`, changes);
}
});
Combat Tracking Hook
const combatHookId = BlacksmithHookManager.registerHook({
name: 'updateCombat',
description: 'My module: Track combat changes',
context: 'my-combat-tracker',
priority: 2, // High priority - core functionality
callback: (combat, changed) => {
if (changed.round !== undefined) {
console.log(`Round changed to ${changed.round}`);
}
}
});
UI Enhancement Hook
const uiHookId = BlacksmithHookManager.registerHook({
name: 'renderChatMessage',
description: 'My module: Enhance chat messages',
context: 'my-chat-enhancer',
priority: 4, // Low priority - UI updates
callback: (message, html, data) => {
html.find('.message-content').addClass('my-enhanced-style');
}
});
One-time Hook with Auto-cleanup
const welcomeHookId = BlacksmithHookManager.registerHook({
name: 'userLogin',
description: 'My module: Welcome message',
context: 'my-welcome',
priority: 5,
options: { once: true }, // Auto-cleanup after first execution
callback: (user) => {
console.log(`Welcome back, ${user.name}!`);
}
});
Performance-Optimized Hooks
// Throttle noisy hooks (e.g., updateToken)
const throttledHookId = BlacksmithHookManager.registerHook({
name: 'updateToken',
description: 'My module: Throttled token updates',
context: 'my-token-tracker',
priority: 4,
options: { throttleMs: 50 }, // Max once per 50ms
callback: (token, changes) => {
console.log('Token updated:', token.name);
}
});
// Debounce for final state (e.g., search input)
const debouncedHookId = BlacksmithHookManager.registerHook({
name: 'searchInput',
description: 'My module: Debounced search',
context: 'my-search',
priority: 4,
options: { debounceMs: 300 }, // Wait 300ms after last input
callback: (input) => {
console.log('Searching for:', input);
}
});
Priority Guidelines
| Priority | Level | Use For |
|---|---|---|
| 1 | CRITICAL | System cleanup, critical features, must-run-first operations |
| 2 | HIGH | Core functionality, data validation, early processing |
| 3 | NORMAL | Most hooks, standard functionality (default) |
| 4 | LOW | Nice-to-have features, UI updates |
| 5 | LOWEST | Cosmetic features, debug hooks |
// Priority 1 - System integrity
BlacksmithHookManager.registerHook({ name: 'closeGame', priority: 1, ... });
// Priority 2 - Core features
BlacksmithHookManager.registerHook({ name: 'updateActor', priority: 2, ... });
// Priority 3 - Standard (default)
BlacksmithHookManager.registerHook({ name: 'renderChatMessage', priority: 3, ... });
// Priority 4 - UI enhancements
BlacksmithHookManager.registerHook({ name: 'renderApplication', priority: 4, ... });
// Priority 5 - Debug/cosmetic
BlacksmithHookManager.registerHook({ name: 'renderPlayerList', priority: 5, ... });
Performance Options
Throttling
Limit execution frequency for noisy hooks:
BlacksmithHookManager.registerHook({
name: 'updateToken',
options: { throttleMs: 50 }, // Max once per 50ms
callback: (token, changes) => { /* ... */ }
});
Debouncing
Wait for activity to settle before running:
BlacksmithHookManager.registerHook({
name: 'searchInput',
options: { debounceMs: 300 }, // Wait 300ms after last call
callback: (input) => { /* ... */ }
});
One-time Hooks
Auto-remove after first execution:
BlacksmithHookManager.registerHook({
name: 'userLogin',
options: { once: true },
callback: (user) => { /* ... */ }
});
Console Commands
Use these to debug and inspect hook registrations:
// Show hook summary (count, names)
BlacksmithAPIHooks()
// Detailed hook information by priority
BlacksmithAPIHookDetails()
// Raw hook statistics
BlacksmithAPIHookStats()
// Expanded details with full formatting
BlacksmithAPIHookExpandedDetails()
| Command | Returns | Description |
|---|---|---|
BlacksmithAPIHooks() |
Object | Hook count, names, and summary data |
BlacksmithAPIHookDetails() |
Object | Hooks organized by priority levels |
BlacksmithAPIHookStats() |
Object | Statistical breakdown by priority/context |
BlacksmithAPIHookExpandedDetails() |
Console Output | Detailed hook information with full formatting |
Best Practices
1. Use Direct Global Access
// GOOD: Use global objects directly
BlacksmithHookManager.registerHook(...)
// ALTERNATIVE: Store reference if preferred
const hookManager = BlacksmithHookManager;
2. Always Use Contexts
// GOOD: Descriptive context for cleanup
BlacksmithHookManager.registerHook({
name: 'updateActor',
context: 'my-module-actor-tracking',
callback: () => { /* ... */ }
});
// BAD: No context makes cleanup difficult
BlacksmithHookManager.registerHook({
name: 'updateActor',
callback: () => { /* ... */ }
});
3. Provide Clear Descriptions
// GOOD: Clear, descriptive
BlacksmithHookManager.registerHook({
name: 'updateActor',
description: 'My Module: Track actor HP changes for health panel updates',
callback: () => { /* ... */ }
});
// BAD: Vague
BlacksmithHookManager.registerHook({
name: 'updateActor',
description: 'Updates stuff',
callback: () => { /* ... */ }
});
4. Use Appropriate Priorities
- Use priority 3 (NORMAL) for most hooks
- Only use 1 or 2 for critical/core functionality
- Use 4 or 5 for cosmetic/debug features
5. Clean Up When Done
const myHookIds = [];
myHookIds.push(BlacksmithHookManager.registerHook({
name: 'updateActor',
context: 'my-module',
callback: () => { /* ... */ }
}));
// Clean up when module disables
Hooks.once('closeGame', () => {
myHookIds.forEach(id => BlacksmithHookManager.removeCallback(id));
});
Debugging and Troubleshooting
Common Issues
Issue: "HookManager is not defined"
// Solution: Use global objects directly
BlacksmithHookManager.registerHook(...)
Issue: Hook not executing
// Check if hook is registered
const stats = BlacksmithHookManager.getStats();
console.log('Registered hooks:', stats.hooks);
// Verify hook name is correct (FoundryVTT hook names are case-sensitive)
Issue: Performance problems
// Use throttling for noisy hooks
BlacksmithHookManager.registerHook({
name: 'updateToken',
options: { throttleMs: 100 },
callback: () => { /* ... */ }
});
// Use debouncing for user input
BlacksmithHookManager.registerHook({
name: 'searchInput',
options: { debounceMs: 500 },
callback: () => { /* ... */ }
});
Error Handling Pattern
try {
if (!BlacksmithHookManager) {
throw new Error('HookManager not available');
}
const hookId = BlacksmithHookManager.registerHook({
name: 'updateActor',
description: 'My module: Actor update handler',
context: 'my-module',
priority: 3,
callback: (actor, changes) => { /* ... */ }
});
console.log('Hook registered successfully:', hookId);
} catch (error) {
console.error('Failed to register hook:', error);
// Fallback to direct FoundryVTT hooks
Hooks.on('updateActor', (actor, changes) => {
// My logic here
});
}
API Test Example
From the integration test suite in api-core.md:
const hookName = 'createActor';
const hookContext = 'my-module-id';
const hookResult = BlacksmithHookManager.registerHook({
name: hookName,
description: 'API Test Hook',
context: hookContext,
priority: 50,
key: `${hookContext}-${hookName}`,
options: {},
callback: async (actor) => {
BlacksmithUtils.postConsoleAndNotification(
'my-module-id',
'Hook triggered!',
{ actorId: actor.id, name: actor.name },
false,
false
);
}
});
console.log('Hook registration result:', hookResult);
Related Documentation
- API Core - General integration setup, module registration, and other APIs
- Architecture Hook Manager - Internal architecture (for Blacksmith developers)