Chat with us, powered by LiveChat
← Return to MyDisct Solver

Extension MPB (MyDisct Pulse Bridge)

MPB is the live telemetry bridge used by the MyDisct Solver browser extension. It reports what the extension is solving in real time, exposes normalized task status endpoints, and lets your automation scripts track processing and completion states with stable task IDs.

What MPB Is

MPB is extension-side runtime visibility. It is independent from createTask/fetchResult API flow and mirrors the extension's own live state. If extension starts solving any supported captcha type, MPB reports that task as processing. When extension finishes, MPB reports completed or failed.

About Captcha Type Examples

Some examples in this page use HCAPTCHA for readability. The same MPB workflow and status model apply to all captcha types supported by the extension.

Core Concept

The extension sends periodic snapshots to your local bridge server, and the bridge converts these snapshots into queryable task records. Your Puppeteer or automation process only needs to read MPB endpoints to understand what the extension is doing now.

Layer Responsibility Output
Browser Extension Detects captcha, starts solve flow, emits progress and result events Live snapshots with active tasks and events
MPB Bridge Server Receives snapshots, builds in-memory task index, normalizes fields Task list and task status endpoints
Your Automation App Polls task IDs and status transitions Reliable processing/completed/failed tracking

Setup and Activation

Step 1: Start Bridge Server

Run the MPB bridge locally. Default address is http://127.0.0.1:3000.

Bash
node mpb-bridge-server.js

Step 2: Configure Extension MPB URL

Open extension popup settings and enable MPB. Set localhost bridge URL to your running server, for example http://127.0.0.1:3000.

Step 3: Verify Connectivity

Use the base endpoint to confirm the bridge is reachable.

cURL
curl http://127.0.0.1:3000/

Expected Response

JSON
{
  "ok": true,
  "service": "MyDisct Pulse Bridge",
  "endpoint": "/mpb/v1/active-captchas",
  "tasks_endpoint": "/mpb/v1/tasks",
  "task_status_endpoint": "/mpb/v1/tasks/:taskId"
}

Snapshot Endpoint

POST /mpb/v1/active-captchas

This endpoint is called by the extension. It sends current active captcha list and event stream. You normally do not post manually in production, but it is useful for testing.

POST Body Fields

Field Type Required Description
source string optional Snapshot source name, usually mydisct_solver
mode string optional Operating mode, usually extension_live
ts number optional Unix milliseconds timestamp
active_captchas array required List of currently processing extension tasks
events array required Task event list including status transitions

Example Snapshot Payload

JSON
{
  "source": "mydisct_solver",
  "mode": "extension_live",
  "ts": 1760645076572,
  "active_captchas": [
    {
      "extension_task_id": "mpb_mlpm1zv1_svwo4j",
      "captcha_type": "HCAPTCHA",
      "status": "processing",
      "tab_id": 7,
      "frame_id": 0,
      "page_url": "https://target-site.example/login"
    }
  ],
  "events": [
    {
      "extension_task_id": "mpb_mlpm1zv1_svwo4j",
      "captcha_type": "HCAPTCHA",
      "status": "processing",
      "source": "mydisct_solver",
      "ts": 1760645076572
    }
  ]
}

Acknowledgement Response

JSON
{
  "ok": true,
  "total_active": 1,
  "total_events": 3,
  "total_tasks": 8
}
GET /mpb/v1/active-captchas

Returns the latest raw snapshot exactly as received by the bridge.

Task Endpoints

GET /mpb/v1/tasks

Returns normalized task records built from active snapshots and events. This is the recommended endpoint for automation dashboards.

Query Parameters

Parameter Type Required Description
status string optional Filter by task status: processing, completed, failed
captcha_type string optional Filter by extension captcha type such as HCAPTCHA or RECAPTCHA

Example Request

cURL
curl "http://127.0.0.1:3000/mpb/v1/tasks?status=processing&captcha_type=HCAPTCHA"

Example Response

JSON
{
  "ok": true,
  "total": 1,
  "tasks": [
    {
      "task_id": "mpb_mlpm1zv1_svwo4j",
      "extension_task_id": "mpb_mlpm1zv1_svwo4j",
      "captcha_type": "HCAPTCHA",
      "status": "processing",
      "source": "mydisct_solver",
      "trigger": "",
      "tab_id": 7,
      "frame_id": 0,
      "page_url": "https://target-site.example/login",
      "created_at": "2026-02-16T20:12:18.737Z",
      "updated_at": "2026-02-16T20:12:22.119Z"
    }
  ]
}
GET /mpb/v1/tasks/:taskId

Returns a single task by id. Use this endpoint for deterministic per-task polling.

Example Request

cURL
curl "http://127.0.0.1:3000/mpb/v1/tasks/mpb_mlpm1zv1_svwo4j"

Completed Response Example

JSON
{
  "ok": true,
  "task": {
    "task_id": "mpb_mlpm1zv1_svwo4j",
    "extension_task_id": "mpb_mlpm1zv1_svwo4j",
    "captcha_type": "HCAPTCHA",
    "status": "completed",
    "source": "mydisct_solver",
    "trigger": "",
    "tab_id": 7,
    "frame_id": 0,
    "page_url": "https://target-site.example/login",
    "created_at": "2026-02-16T20:12:18.737Z",
    "updated_at": "2026-02-16T20:12:43.379Z"
  }
}

Task Lifecycle and Status Semantics

MPB task state is event-driven. The extension emits lifecycle transitions and bridge keeps the latest status. Typical transition path is processing to completed. Failures end with failed.

Status Meaning Action in Automation
processing Extension detected captcha and solve flow is running Keep polling task endpoint
completed Solve flow finished successfully in extension context Continue automation step
failed Solve flow ended unsuccessfully Retry or fallback strategy

Timeline Example

Log Timeline
[20:04:46] processing: mpb_mlplsb3l_etb204:HCAPTCHA
[20:04:56] completed:  mpb_mlplsb3l_etb204:HCAPTCHA

Automation Integration Example (Node.js)

This example shows a practical task-based polling strategy. It fetches active processing tasks, then waits until each task becomes completed or failed.

JavaScript
const MPB_BASE = 'http://127.0.0.1:3000';

async function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function getProcessingTasks() {
  const res = await fetch(MPB_BASE + '/mpb/v1/tasks?status=processing');
  if (!res.ok) {
    throw new Error('MPB tasks endpoint error: ' + res.status);
  }
  const data = await res.json();
  return Array.isArray(data.tasks) ? data.tasks : [];
}

async function waitTask(taskId, timeoutMs) {
  const startedAt = Date.now();
  while (Date.now() - startedAt < timeoutMs) {
    const res = await fetch(MPB_BASE + '/mpb/v1/tasks/' + encodeURIComponent(taskId));

    if (res.status === 404) {
      await sleep(500);
      continue;
    }

    if (!res.ok) {
      throw new Error('MPB task endpoint error: ' + res.status);
    }

    const data = await res.json();
    const status = String(data.task && data.task.status || '').toLowerCase();

    if (status === 'completed') {
      return { ok: true, task: data.task };
    }

    if (status === 'failed') {
      return { ok: false, reason: 'failed', task: data.task };
    }

    await sleep(700);
  }

  return { ok: false, reason: 'timeout' };
}

async function monitorLoop() {
  const seen = new Set();

  while (true) {
    const tasks = await getProcessingTasks();

    for (const task of tasks) {
      const taskId = task.task_id;
      if (!taskId || seen.has(taskId)) {
        continue;
      }

      seen.add(taskId);
      const result = await waitTask(taskId, 120000);
      console.log('Task result:', taskId, result);
    }

    await sleep(1000);
  }
}

monitorLoop().catch((error) => {
  console.error('Monitor failed:', error.message);
  process.exit(1);
});

Full Bridge Source (mpb-bridge-server.js)

The complete bridge implementation is included below.

mpb-bridge-server.js
const http = require('http');

const HOST = process.env.MPB_HOST || '127.0.0.1';
const PORT = Number(process.env.MPB_PORT || 3000);

let state = {
  source: 'mpb-local-server',
  mode: 'extension_live',
  ts: Date.now(),
  active_captchas: [],
  events: []
};
const taskStore = new Map();
let lastStateDigest = '';

function sendJson(res, statusCode, payload) {
  const body = JSON.stringify(payload);
  res.writeHead(statusCode, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Private-Network': 'true',
    'Content-Length': Buffer.byteLength(body)
  });
  res.end(body);
}

function readBody(req) {
  return new Promise((resolve, reject) => {
    let data = '';
    req.on('data', (chunk) => {
      data += chunk;
      if (data.length > 1024 * 1024) {
        reject(new Error('Payload too large'));
      }
    });
    req.on('end', () => resolve(data));
    req.on('error', reject);
  });
}

function normalizeTaskId(item) {
  const raw = item?.extension_task_id || item?.task_id || '';
  return String(raw || '').trim();
}

function normalizeTaskStatus(status) {
  return String(status || '').trim().toLowerCase();
}

function toIsoFromTs(ts) {
  if (typeof ts === 'number' && Number.isFinite(ts) && ts > 0) {
    return new Date(ts).toISOString();
  }
  return new Date().toISOString();
}

function ingestStateIntoTaskStore(snapshot) {
  const activeCaptchas = Array.isArray(snapshot?.active_captchas) ? snapshot.active_captchas : [];
  const events = Array.isArray(snapshot?.events) ? snapshot.events : [];

  for (const item of activeCaptchas) {
    const taskId = normalizeTaskId(item);
    if (!taskId) {
      continue;
    }

    const existing = taskStore.get(taskId) || {};
    const nowIso = toIsoFromTs(snapshot?.ts);
    taskStore.set(taskId, {
      task_id: taskId,
      extension_task_id: taskId,
      captcha_type: item?.captcha_type || existing.captcha_type || '',
      status: 'processing',
      source: item?.source || snapshot?.source || existing.source || 'mydisct_solver',
      trigger: item?.trigger || existing.trigger || '',
      tab_id: item?.tab_id ?? existing.tab_id ?? -1,
      frame_id: item?.frame_id ?? existing.frame_id ?? 0,
      page_url: item?.page_url || existing.page_url || '',
      created_at: existing.created_at || item?.created_at || nowIso,
      updated_at: item?.updated_at || nowIso
    });
  }

  for (const event of events) {
    const taskId = normalizeTaskId(event);
    if (!taskId) {
      continue;
    }

    const existing = taskStore.get(taskId) || {};
    const eventStatus = normalizeTaskStatus(event?.status) || existing.status || 'processing';
    const eventIso = toIsoFromTs(event?.ts);
    taskStore.set(taskId, {
      task_id: taskId,
      extension_task_id: taskId,
      captcha_type: event?.captcha_type || existing.captcha_type || '',
      status: eventStatus,
      source: event?.source || existing.source || snapshot?.source || 'mydisct_solver',
      trigger: event?.trigger || existing.trigger || '',
      tab_id: event?.tab_id ?? existing.tab_id ?? -1,
      frame_id: event?.frame_id ?? existing.frame_id ?? 0,
      page_url: event?.page_url || existing.page_url || '',
      created_at: existing.created_at || event?.started_at || eventIso,
      updated_at: event?.ended_at || eventIso
    });
  }
}

function buildStateDigest(snapshot) {
  const activeCaptchas = Array.isArray(snapshot?.active_captchas) ? snapshot.active_captchas : [];
  const events = Array.isArray(snapshot?.events) ? snapshot.events : [];

  const compactActive = activeCaptchas.map((item) => ({
    id: normalizeTaskId(item),
    type: item?.captcha_type || '',
    status: item?.status || 'processing'
  }));
  const compactEvents = events.map((item) => ({
    id: normalizeTaskId(item),
    status: item?.status || '',
    ts: item?.ts || 0
  }));

  return JSON.stringify({
    source: snapshot?.source || '',
    mode: snapshot?.mode || '',
    active: compactActive,
    events: compactEvents
  });
}

function getTaskList(statusFilter, captchaTypeFilter) {
  const normalizedStatus = normalizeTaskStatus(statusFilter);
  const normalizedCaptcha = String(captchaTypeFilter || '').trim().toUpperCase();

  const list = Array.from(taskStore.values()).filter((task) => {
    if (normalizedStatus && normalizeTaskStatus(task.status) !== normalizedStatus) {
      return false;
    }
    if (normalizedCaptcha && String(task.captcha_type || '').toUpperCase() !== normalizedCaptcha) {
      return false;
    }
    return true;
  });

  list.sort((a, b) => {
    const aTime = Date.parse(a.updated_at || a.created_at || 0) || 0;
    const bTime = Date.parse(b.updated_at || b.created_at || 0) || 0;
    return bTime - aTime;
  });

  return list;
}

const server = http.createServer(async (req, res) => {
  const reqUrl = new URL(req.url, `http://${req.headers.host || `${HOST}:${PORT}`}`);
  const path = reqUrl.pathname.replace(//+$/, '') || '/';
  const now = new Date().toISOString();

  if (req.method === 'OPTIONS') {
    process.stdout.write(`[${now}] OPTIONS ${path}
`);
    sendJson(res, 200, { ok: true });
    return;
  }

  if (req.method === 'GET' && path === '/') {
    process.stdout.write(`[${now}] GET /
`);
    sendJson(res, 200, {
      ok: true,
      service: 'MyDisct Pulse Bridge',
      endpoint: '/mpb/v1/active-captchas',
      tasks_endpoint: '/mpb/v1/tasks',
      task_status_endpoint: '/mpb/v1/tasks/:taskId'
    });
    return;
  }

  if (req.method === 'GET' && path === '/mpb/v1/active-captchas') {
    process.stdout.write(`[${now}] GET /mpb/v1/active-captchas total=${state.active_captchas.length} events=${state.events.length}
`);
    sendJson(res, 200, state);
    return;
  }

  if (req.method === 'GET' && path === '/mpb/v1/tasks') {
    const status = reqUrl.searchParams.get('status') || '';
    const captchaType = reqUrl.searchParams.get('captcha_type') || '';
    const tasks = getTaskList(status, captchaType);
    process.stdout.write(`[${now}] GET /mpb/v1/tasks total=${tasks.length}
`);
    sendJson(res, 200, {
      ok: true,
      total: tasks.length,
      tasks
    });
    return;
  }

  if (req.method === 'GET' && path.startsWith('/mpb/v1/tasks/')) {
    const taskId = decodeURIComponent(path.slice('/mpb/v1/tasks/'.length));
    const task = taskStore.get(taskId);
    if (!task) {
      sendJson(res, 404, {
        ok: false,
        error: 'Task not found',
        task_id: taskId
      });
      return;
    }

    process.stdout.write(`[${now}] GET /mpb/v1/tasks/${taskId} status=${task.status}
`);
    sendJson(res, 200, {
      ok: true,
      task
    });
    return;
  }

  if (req.method === 'POST' && path === '/mpb/v1/active-captchas') {
    try {
      const bodyText = await readBody(req);
      const payload = bodyText ? JSON.parse(bodyText) : {};
      state = {
        source: payload.source || 'mydisct_solver',
        mode: payload.mode || 'extension_live',
        ts: typeof payload.ts === 'number' ? payload.ts : Date.now(),
        active_captchas: Array.isArray(payload.active_captchas) ? payload.active_captchas : [],
        events: Array.isArray(payload.events) ? payload.events : []
      };
      ingestStateIntoTaskStore(state);

      const digest = buildStateDigest(state);
      const isChanged = digest !== lastStateDigest;
      lastStateDigest = digest;

      const preview = state.active_captchas
        .slice(0, 5)
        .map((item) => `${item?.extension_task_id || item?.task_id || '-'}:${item?.captcha_type || '-'}`)
        .join(',');
      const lastEvent = state.events.length > 0 ? state.events[state.events.length - 1] : null;
      const eventPreview = lastEvent ? `${lastEvent.status || '-'}:${lastEvent.extension_task_id || lastEvent.task_id || '-'}:${lastEvent.captcha_type || '-'}` : '-';
      if (isChanged) {
        process.stdout.write(`[${now}] POST /mpb/v1/active-captchas source=${state.source} mode=${state.mode} total=${state.active_captchas.length} tasks=${preview} events=${state.events.length} last=${eventPreview}
`);
      }
      sendJson(res, 200, {
        ok: true,
        total_active: state.active_captchas.length,
        total_events: state.events.length,
        total_tasks: taskStore.size
      });
      return;
    } catch (error) {
      process.stdout.write(`[${now}] POST /mpb/v1/active-captchas error=${error.message}
`);
      sendJson(res, 400, {
        ok: false,
        error: error.message
      });
      return;
    }
  }

  process.stdout.write(`[${now}] ${req.method} ${path} 404
`);
  sendJson(res, 404, {
    ok: false,
    error: 'Not found'
  });
});

server.listen(PORT, HOST, () => {
  process.stdout.write(`MPB bridge listening on http://${HOST}:${PORT}
`);
});

Best Practices

Recommendations
  • Use /mpb/v1/tasks/:taskId polling for deterministic task tracking instead of parsing console logs.
  • Keep polling interval between 500ms and 1000ms for responsive updates without unnecessary load.
  • Treat processing as transient state and always wait for completed or failed.
  • Filter by captcha_type when your automation targets specific captcha families.
  • Persist task IDs in your automation layer if you need auditability beyond bridge process lifetime.

Troubleshooting

Saved, but localhost bridge is unreachable

Bridge process is not running or URL is incorrect. Start bridge first, then verify extension MPB URL and port match exactly.

Active count is zero while you expect solving

Zero active means extension currently has no processing task in snapshot. Confirm captcha module is enabled, captcha is detected on page, and extension is in active solving flow.

Repeated POST logs are visible

Repeated posts are normal heartbeat behavior. For stable integration, use task-based polling endpoints instead of relying on console frequency.