Skip to content

A robust JavaScript (NPM: flock-election) library for implementing the Leader Election Algorithm in distributed environments (tabs, windows, and processes) using BroadcastChannel. Ensures only one instance handles critical tasks

License

Notifications You must be signed in to change notification settings

foroughi1380/flock.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

14 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐦 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:

  1. All active instances behave like members of a "flock".
  2. Flock.js quickly and reliably selects only one of these instances to be the "Leader."
  3. 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-election

Browser 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).

About

A robust JavaScript (NPM: flock-election) library for implementing the Leader Election Algorithm in distributed environments (tabs, windows, and processes) using BroadcastChannel. Ensures only one instance handles critical tasks

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published