flock-election
v0.1.7
Published
A robust JavaScript library for implementing the Leader Election algorithm across distributed environments (tabs, windows, and processes). It ensures only one instance handles critical tasks and provides reliable inter-instance communication.
Maintainers
Readme
🐦 Flock.js (NPM : flock-election): Coordination and Leadership Across Browser Tabs & Process Instances
Flock.js is a lightweight and reliable library that enables your JavaScript application to work effectively in a team environment across multiple tabs, windows, Web Workers, or even multiple instantiated members within a single JavaScript process.
💡 The Problem: Chaos in a Distributed Environment
Imagine your application has several active instances running simultaneously—whether they are spread across four browser tabs or represented by four distinct FlockMember objects within the same window. You don't want all of them to send an analytics report or execute a critical database update at the exact same time. If this happens, resources are wasted, and your data integrity is compromised (creating chaos).
✨ The Solution: The Leader Election Algorithm
Flock.js solves this problem by implementing the Leader Election Algorithm:
- All active instances behave like members of a "flock".
- Flock.js quickly and reliably selects only one of these instances to be the "Leader."
- The Leader is the sole member permitted to execute critical and sensitive tasks (such as sending important reports, performing database updates, or controlling shared resources).
How It Works (Synchronization)
This synchronization between instances is achieved using internal browser communication channels (BroadcastChannel), or through LocalStorage as a fallback. This ensures that all members constantly know who the Leader is, and if the Leader fails or disconnects, a new Leader is immediately elected.
The Result: Stable performance, resource savings, and the assurance that no critical task is ever performed more than once.
Installation and Setup
NPM / Yarn
Install the library using the Node Package Manager:
npm install flock-election
# or
yarn add flock-electionBrowser Usage (CDN/Module)
If you are not using a bundler (like Webpack/Rollup), you must load the files in the correct order:
<script src="path/to/flockSingleton.js"></script>
<script src="path/to/flockMember.js"></script>
<script>
const myMember = new FlockMember({ channelName: 'my_app_flock' });
// ...
</script>Configuration and Member Creation
To create a new member of the flock, you must instantiate FlockMember with your desired configuration options.
FlockMember(options)
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| channelName | string | 'flock_channel_v1' | Most crucial setting. The name of the channel where this flock communicates. Essential for isolating different flocks. |
| heartbeatInterval | number | 2000 | The time interval (in milliseconds) at which the Leader sends its Heartbeat. |
| heartbeatTtl | number | 5000 | The Time-To-Live (in milliseconds). If the Leader's Heartbeat is not received within this time, the Leader is presumed dead, and a new election begins. |
| debug | boolean | false | Enables internal library logging. |
Member Creation Example
import FlockMember from 'flock-election';
const myMember = new FlockMember({
channelName: 'finance_dashboard',
heartbeatInterval: 1000, // Heartbeat every 1 second
heartbeatTtl: 4000 // Leader timeout after 4 seconds of silence
});
// This member automatically starts participating in the election upon instantiation.Core Functionality and Communication with the Leader
Flock.js provides two main communication types: Request/Response and One-way Message (Fire-and-Forget).
1. Request and Await Response (sendRequest)
This method returns a Promise and is used for operations that require a result (e.g., fetching data, calculations). If the request times out or the Leader dies, the request is moved to the Retry Queue.
| Argument | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| data | Object | (Required) | The payload data to be sent to the Leader. |
| options | Object | {} | Includes { timeout: number }. Sets the maximum time to wait for a reply. Use this if your task takes longer than the default TTL. |
| callback | Function | null | (Optional) A traditional alternative to the Promise, with signature (error, response) => {}. |
// Example using Promise and Custom Timeout
myMember.sendRequest(
{ action: 'get_heavy_data', id: 42 },
{ timeout: 15000 } // Waits up to 15 seconds for a response
)
.then(response => {
console.log("Response from Leader:", response);
})
.catch(error => {
// This error occurs only after the final TTL/Timeout has expired and all retries have failed.
console.error("Request failed after final retries:", error.message);
});2. Send One-way Message (sendMessageToLeader)
This method is used for notifications or reporting and does not wait for a response. If the Leader does not acknowledge the message within the TTL, the message is placed in the Retry Queue and resent after a new Leader is elected.
// Send a critical log or alert
myMember.sendMessageToLeader({
level: 'ALERT',
message: 'User session expired. Force logout needed.'
});Leader Logic Implementation
The following code should only be defined once in your application, as only the actual Leader will execute the logic.
1. Responding to Requests (onRequest)
This function accepts a callback, which in turn receives two arguments: data and the reply function. The Leader must eventually call reply(responseData) to send the answer back, otherwise, the Requester will timeout.
myMember.onRequest((data, reply) => {
console.log(`[Leader] received request:`, data);
if (data.action === 'calculate') {
// Asynchronous operations (like fetching from a database) can occur here
fetch('/api/heavy-calc', { method: 'POST', body: JSON.stringify(data) })
.then(res => res.json())
.then(result => {
// Successfully send the response back to the requester
reply({ status: 'Completed', result: result });
})
.catch(err => {
// Send an error response (reply must still be called)
reply({ status: 'Error', message: err.message });
});
} else {
reply({ status: 'Ignored', message: 'Action not supported' });
}
});2. Reacting to Leadership Changes (onLeadershipChange)
This event fires whenever your membership status changes (e.g., you become the Leader, or a new Leader is found).
myMember.onLeadershipChange((leader_id) => {
if (myMember.isLeader()) {
console.log(`🎉 I am the new Leader! My ID: ${myMember.id}`);
// Start Leader-specific timed tasks here
} else {
console.log(`Not the Leader. New Leader ID is: ${leader_id}. Stopping all Leader duties.`);
// Clear any timed loops or resources
}
});📩 Handling Messages (Standardized Envelope)
The onMessage method receives a unified Message Envelope for all communication types. This makes it easy to handle broadcasts, direct messages, and leader notifications in one place.
| Property | Description |
| :--- | :--- |
| senderId | The ID of the member who sent the message. |
| type | Can be 'broadcast', 'direct-message', or 'leader-message'. |
| payload | The actual data sent with the message. |
myMember.onMessage((msg) => {
// 'msg' is always an object: { senderId, type, payload }
switch (msg.type) {
case 'broadcast':
// Sent by Leader -> Received by All Members
console.log(`📢 Broadcast from Leader (${msg.senderId}):`, msg.payload);
break;
case 'direct-message':
// Sent by Leader -> Received by ONLY this Member
console.log(`📨 Private message from Leader (${msg.senderId}):`, msg.payload);
break;
case 'leader-message':
// Sent by a Member -> Received by the LEADER
// (Only triggers if myMember.isLeader() is true)
console.log(`🔔 Notification from Member (${msg.senderId}):`, msg.payload);
break;
}
});
Utility Methods
| Method | Description |
| :--- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| myMember.isLeader() | Returns whether this instance is currently the Leader (boolean). |
| myMember.resign() | Permanently removes the member from the flock and stops all monitoring and participation. If the resigning member was the leader, this action triggers a new election. Use this method only when the member is intentionally shutting down or leaving the application entirely. |
| myMember.cedeLeadership() | (Leader Only) Voluntarily steps down from the leadership role, triggering a new election, while remaining an active member of the flock to receive subsequent updates. This method uses a Temporary Exclusion mechanism to prevent the ceding member from immediately reclaiming leadership. |
| myMember.getMembersInfo() | (Leader Only) Returns a list of IDs of all active members in the flock (whose status is maintained within the TTL). |
| myMember.sendToMember(id, data) | (Leader Only) Send a direct message to a specific member ID (no built-in retry). |
| myMember.broadcastToMembers(data) | (Leader Only) Broadcast a message to all members of the flock (no built-in retry). |
