API: Sockets - Drowbe/coffee-pub-blacksmith GitHub Wiki
For External Module Developers Only
This document covers how other FoundryVTT modules can use Blacksmith's socket management system for cross-client communication.
If you're developing Blacksmith itself, see
architecture-socketmanager.mdfor internal architecture details.
Audience: Developers integrating with Blacksmith and leveraging the exposed API.
Blacksmith provides a unified socket management API that handles SocketLib integration, fallback to native Foundry sockets, and automatic initialization. This allows other modules to leverage Blacksmith's socket infrastructure without managing SocketLib setup themselves.
- ✅ Automatic SocketLib Integration - Handles SocketLib detection and registration
- ✅ Native Fallback - Automatically falls back to Foundry's native socket system if SocketLib isn't available
- ✅ Timing-Safe - Provides
waitForReady()to handle initialization timing - ✅ Unified Interface - Same API regardless of underlying transport (SocketLib or native)
- ✅ Event Registration - Simple
register()andemit()methods
import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
// Get the socket API
const blacksmith = await BlacksmithAPI.get();
const sockets = blacksmith.sockets;
// Or use the convenience method
const sockets = await BlacksmithAPI.getSockets();const blacksmith = game.modules.get('coffee-pub-blacksmith')?.api;
if (blacksmith?.sockets) {
const sockets = blacksmith.sockets;
}// Note: This is set asynchronously, may not be available immediately
if (window.Blacksmith?.socket) {
const sockets = window.Blacksmith.socket;
}Wait for the socket system to be ready before using it.
Returns: Promise<boolean> - Resolves to true when socket is ready
Example:
await sockets.waitForReady();
console.log('Socket system is ready!');When to use: Always call this before registering handlers or emitting messages if your code runs early in the module lifecycle.
Register a socket event handler to receive messages from other clients.
Parameters:
-
eventName(string, required): The event name to listen for -
handler(function, required): The callback function that receives(data, userId)when the event fires
Returns: Promise<boolean> - Resolves to true if registration succeeded
Example:
// Wait for socket to be ready
await sockets.waitForReady();
// Register a handler
await sockets.register('myModule.customEvent', (data, userId) => {
console.log(`Received data from user ${userId}:`, data);
// Handle the socket message
});
// Or chain them
sockets.waitForReady().then(() => {
return sockets.register('myModule.anotherEvent', (data) => {
// Handle event
});
});Best Practices:
- Use module-specific event names (e.g.,
'my-module.eventName') to avoid conflicts - Always wait for socket to be ready before registering
- Register handlers in a
readyhook or after confirming socket readiness
Emit a socket message to other clients.
Parameters:
-
eventName(string, required): The event name to emit -
data(any, required): The data to send (can be any serializable JavaScript object) -
options(object, optional): SocketLib options-
userId(string, optional): Send to specific user only -
recipients(array, optional): Array of user IDs to send to - Other SocketLib options (see SocketLib documentation)
-
Returns: Promise<boolean> - Resolves to true if emit succeeded
Example:
// Wait for socket to be ready
await sockets.waitForReady();
// Emit to all clients
await sockets.emit('myModule.customEvent', {
message: 'Hello from my module!',
timestamp: Date.now()
});
// Emit to specific user
await sockets.emit('myModule.customEvent', {
message: 'Private message'
}, {
userId: 'specific-user-id'
});
// Emit to multiple users
await sockets.emit('myModule.customEvent', {
message: 'Group message'
}, {
recipients: ['user1-id', 'user2-id']
});Check if the socket system is currently ready.
Returns: boolean - true if ready, false otherwise
Example:
if (sockets.isReady()) {
// Socket is ready, safe to use
await sockets.emit('myEvent', { data: 'test' });
} else {
// Wait for ready first
await sockets.waitForReady();
await sockets.emit('myEvent', { data: 'test' });
}Check which transport layer is being used.
Returns: boolean - true if using SocketLib, false if using native Foundry sockets
Example:
const usingSocketLib = sockets.isUsingSocketLib();
console.log(`Using ${usingSocketLib ? 'SocketLib' : 'native Foundry sockets'}`);Note: Most modules don't need to check this - the API abstracts away the transport layer differences.
Get the underlying socket instance (advanced use only).
Returns: Object|null - The socket instance or null if not ready
Warning: Only use this if you need direct access to SocketLib methods. Prefer using the wrapper methods (register, emit) instead.
Example:
const socket = sockets.getSocket();
if (socket && socket.emitToOthers) {
// Use SocketLib-specific method
socket.emitToOthers('event', data);
}Here's a complete example of a module using the Socket API:
import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
Hooks.once('ready', async () => {
// Get Blacksmith API
const blacksmith = await BlacksmithAPI.get();
// Wait for socket system to be ready
await blacksmith.sockets.waitForReady();
// Register a handler for incoming messages
await blacksmith.sockets.register('my-module.syncData', (data, userId) => {
console.log(`Received sync from ${userId}:`, data);
// Update local state based on received data
updateLocalData(data);
});
// Emit a message when local data changes
function onLocalDataChange(newData) {
blacksmith.sockets.emit('my-module.syncData', {
timestamp: Date.now(),
data: newData
});
}
// Example: Emit when a setting changes
Hooks.on('updateSetting', (scope, key, value) => {
if (key === 'my-module.someSetting') {
onLocalDataChange({ setting: value });
}
});
});-
Use Module Prefixes: Always prefix events with your module ID
- ✅ Good:
'my-module.eventName' - ❌ Bad:
'eventName'(might conflict with other modules)
- ✅ Good:
-
Use Descriptive Names: Make event names clear and specific
- ✅ Good:
'cartographer.drawingStarted' - ❌ Bad:
'cartographer.event1'
- ✅ Good:
-
Follow Conventions: Use dot notation for namespacing
- ✅ Good:
'module.category.action' - Example:
'my-module.sync.update'
- ✅ Good:
The socket API methods return Promises that may reject if something goes wrong:
try {
await sockets.emit('myEvent', { data: 'test' });
} catch (error) {
console.error('Failed to emit socket message:', error);
// Handle error (e.g., show user notification, fallback behavior)
}The socket system initializes automatically when Blacksmith loads. However, there may be a delay before it's ready:
- SocketLib Detection: Blacksmith checks for SocketLib
- Fallback Check: If SocketLib isn't found, it waits before falling back to native sockets
-
Ready Hook: Emits
blacksmith.socketReadyhook when ready
Important: The socket API (module.api.sockets) is initialized asynchronously after module.api is created. Use one of these patterns:
Recommended Approach:
// Option 1: Use BlacksmithAPI.getSockets() (handles timing automatically)
Hooks.once('ready', async () => {
const sockets = await BlacksmithAPI.getSockets();
await sockets.waitForReady();
// Now safe to use sockets
await sockets.register('myModule.event', (data) => { ... });
});
// Option 2: Wait for Blacksmith API, then wait for sockets
Hooks.once('ready', async () => {
const blacksmith = await BlacksmithAPI.get();
// Note: blacksmith.sockets may be null initially - wait for it
if (!blacksmith.sockets) {
// Wait up to 2 seconds for socket API to be exposed
let attempts = 0;
while (!blacksmith.sockets && attempts < 20) {
await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
}
}
if (blacksmith.sockets) {
await blacksmith.sockets.waitForReady();
// Now safe to use sockets
}
});
// Option 3: Listen for the ready hook
Hooks.once('blacksmith.socketReady', async () => {
const sockets = await BlacksmithAPI.getSockets();
// Socket is ready, safe to use
});
// Option 4: Direct module access with polling
Hooks.once('ready', async () => {
const module = game.modules.get('coffee-pub-blacksmith');
if (module?.api) {
// Wait for socket API to be exposed
while (!module.api.sockets) {
await new Promise(resolve => setTimeout(resolve, 100));
}
await module.api.sockets.waitForReady();
// Now safe to use
}
});Blacksmith automatically handles the transport layer:
-
SocketLib (Preferred): Used if SocketLib module is installed and active
- Better performance
- More features (targeted messaging, etc.)
- Professional-grade reliability
-
Native Fallback: Used if SocketLib isn't available
- Built into FoundryVTT
- Basic functionality (broadcast to all clients)
- Still reliable for most use cases
You don't need to worry about this - the API abstracts it away. However, sockets.isUsingSocketLib() is available if you need to know which transport is active.
// Manual SocketLib setup
const socket = socketlib.registerModule('my-module');
socket.register('event', handler);
socket.emit('event', data);// Blacksmith handles setup
const sockets = (await BlacksmithAPI.get()).sockets;
await sockets.waitForReady();
await sockets.register('event', handler);
await sockets.emit('event', data);Benefits:
- ✅ No need to detect/manage SocketLib yourself
- ✅ Automatic fallback if SocketLib isn't available
- ✅ Consistent API across Coffee Pub modules
- ✅ Less code to write and maintain
Problem: sockets.isReady() returns false or methods throw errors
Solution: Always wait for ready before using:
await sockets.waitForReady();Problem: Registered handlers aren't being called
Possible Causes:
- Socket not ready when handler was registered
- Event name mismatch between emit and register
- Handler registration failed (check console for errors)
Solution:
// Ensure socket is ready
await sockets.waitForReady();
// Register handler
await sockets.register('my-module.event', (data) => {
console.log('Handler called!', data);
});
// Verify registration
console.log('Handler registered for my-module.event');Problem: emit() succeeds but other clients don't receive messages
Possible Causes:
- Handler not registered on receiving clients
- Network issues
- Module not active on receiving clients
Solution:
- Verify handler is registered on all clients
- Check browser console for errors
- Ensure module is active for all users
-
Internal Architecture: See
architecture-socketmanager.mdfor Blacksmith developers - SocketLib Documentation: See SocketLib documentation for advanced features
-
Blacksmith Core API: See
api-core.mdfor general API access
| Method | Returns | Description |
|---|---|---|
waitForReady() |
Promise<boolean> |
Wait for socket system to be ready |
register(eventName, handler) |
Promise<boolean> |
Register a socket event handler |
emit(eventName, data, options) |
Promise<boolean> |
Emit a socket message |
isReady() |
boolean |
Check if socket is ready |
isUsingSocketLib() |
boolean |
Check which transport is used |
getSocket() |
Object|null |
Get underlying socket (advanced) |
For issues or questions:
- Check this documentation
- Review console logs for errors
- Verify SocketLib is installed (if expecting SocketLib features)
- Contact the Blacksmith development team
Last Updated: Current session
Status: Production ready
API Version: 1.0.0