npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

steadfast-fraud

v1.1.0

Published

Check customer fraud/delivery stats on Steadfast Courier by phone number — handles login, session cookies, host failover, multi-account failover, and 24h auto-refresh automatically.

Readme

fraudjs

Check a customer's delivery and fraud history on Steadfast Courier by phone number. Drop in credentials and just call checkPhone() — login, cookies, host failover, and session refresh are handled automatically.

import { checkPhone } from 'steadfast-fraud';

const result = await checkPhone('01700000000');
// { delivered: 12, cancelled: 1, frauds: 0, total: 13, successRatio: 0.9231, consignment: [...] }

Requirements

  • Node.js ≥ 18 (uses built-in fetch and crypto)
  • An active Steadfast merchant account

Install

npm install steadfast-fraud

Setup

You need at least one Steadfast merchant credential. Two ways to add them:

Option A — .env (quickest)

[email protected]:yourpassword

Multiple accounts for failover (comma-separated, no spaces):

[email protected]:pass1,[email protected]:pass2

Make sure your app loads .env (e.g. with dotenv) or export the variable in your shell.

Option B — addCredential() / CLI (persisted)

Credentials are encrypted with AES-256-GCM and stored in your OS config directory:

  • Linux: ~/.config/fraudjs/credentials.json
  • macOS: ~/Library/Preferences/fraudjs/credentials.json
  • Windows: %APPDATA%\fraudjs\credentials.json
npx fraudjs add-credential
# prompts for email + password (hidden input)

Or from code:

import { addCredential } from 'steadfast-fraud';
await addCredential({ email: '[email protected]', password: 'yourpassword' });

Encryption key

By default a random 32-byte key is generated and saved to ~/.config/fraudjs/master.key. It's machine-specific — credentials can't be decrypted on another machine unless you copy the key file.

For a portable/server setup, set FRAUDJS_SECRET to any string and the key will be derived from it instead:

# generate a good one:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
FRAUDJS_SECRET=<that value>

Configuration

All optional — sensible defaults are built in.

| Variable | Default | What it does | |---|---|---| | FRAUDJS_CREDENTIALS | – | email:password pairs, comma-separated | | FRAUDJS_SECRET | machine key | Key for the encrypted credential store | | FRAUDJS_BASE_URL | steadfast.com.bd, packzy.com, merchant.packzy.com | Host(s) to try, in order. Comma-separated; scheme optional | | FRAUDJS_TIMEOUT_MS | 30000 | Per-request timeout | | FRAUDJS_RETRIES | 2 | Retries for network errors and transient 5xx (never applied to POST /login) |

merchant.packzy.com — the original host — now returns 502 from its load balancer. It is kept last in the default list for backwards compatibility, so existing setups keep working while new lookups go to the live host.


API

checkPhone(phone)

Returns delivery and fraud stats for a phone number. Tries each configured credential in order; if one fails it moves to the next.

The number is normalized first, so all of these are equivalent:

await checkPhone('01712345678');
await checkPhone('+8801712345678');
await checkPhone('017-1234 5678');

Returns:

{
  delivered:    number,   // total successfully delivered orders
  cancelled:    number,   // total cancelled orders
  frauds:       number,   // flagged fraud orders
  total:        number,   // delivered + cancelled
  successRatio: number,   // delivered / total, 0 when there is no history
  consignment:  object[], // raw consignment objects from the Steadfast API
}

Throws:

  • FraudJSError — the phone number isn't a valid Bangladeshi mobile number
  • NoCredentialsError — nothing configured
  • AllCredentialsFailedError — every credential was tried and failed (has .failures array)
  • ServiceUnavailableError — no configured host served the login page (portal down or moved)
  • RateLimitError — login throttled (has .retryAfterSeconds)
  • NetworkError — DNS failure, timeout, etc.

addCredential({ email, password })

Encrypts and saves a credential. Replaces any existing entry for that email.

listCredentials()

Returns stored emails only — passwords are never exposed.

removeCredential(email)

Removes a stored credential. Throws if the email isn't found.

refresh()

Forces a cookie refresh for all configured accounts. You normally don't need this — sessions auto-refresh every 24 hours.

diagnose()

Checks every configured host without logging in, and reports which credentials are visible. Use it to tell "the portal moved" apart from "the password is wrong".

import { diagnose } from 'steadfast-fraud';

const report = await diagnose();
// {
//   hosts: [
//     { baseUrl: 'https://steadfast.com.bd',    status: 200, ok: true,  csrfToken: true,  ms: 165, error: null },
//     { baseUrl: 'https://merchant.packzy.com', status: 502, ok: false, csrfToken: false, ms: 152, error: 'HTTP 502' },
//   ],
//   credentials: ['[email protected]'],
//   ok: true,
// }

Error handling

All errors extend FraudJSError and have a .name matching the class name (useful for switch statements without instanceof):

import {
  checkPhone,
  NoCredentialsError,
  AllCredentialsFailedError,
  AuthError,
  NetworkError,
  ServiceUnavailableError,
  RateLimitError,
} from 'steadfast-fraud';

try {
  return await checkPhone(phone);
} catch (err) {
  switch (err.name) {
    case 'NoCredentialsError':
      console.error('No credentials configured');
      break;
    case 'AllCredentialsFailedError':
      for (const { email, error } of err.failures) {
        console.error(`[${email}] ${error}`);
      }
      break;
    case 'ServiceUnavailableError':
      console.error('Steadfast portal is down or moved — run `fraudjs doctor`');
      break;
    case 'RateLimitError':
      console.error(`Throttled; retry in ${err.retryAfterSeconds ?? 60}s`);
      break;
    case 'NetworkError':
      console.error('Cannot reach Steadfast');
      break;
    default:
      throw err;
  }
}

CLI

npx fraudjs check 01700000000
npx fraudjs doctor
npx fraudjs add-credential
npx fraudjs list-credentials
npx fraudjs remove-credential [email protected]
npx fraudjs refresh

check prints JSON to stdout. Exit code 1 + stderr message on failure.

doctor reports host reachability and configured credentials — run this first when lookups start failing:

$ npx fraudjs doctor
Hosts:
  ok   https://steadfast.com.bd  HTTP 200  165ms
  ok   https://packzy.com  HTTP 200  174ms
  FAIL https://merchant.packzy.com  HTTP 502  152ms  HTTP 502

Credentials:
  [email protected]

How it works

Steadfast uses a standard Laravel session setup. The sequence on first use:

  1. Pick a host — each entry in FRAUDJS_BASE_URL (or the built-in list) is tried in order until one serves the login page. The host that accepts the login is remembered for the rest of the session, since the session cookie is only valid there.
  2. GET /login — pick up the XSRF-TOKEN cookie and the _token CSRF value from the HTML
  3. POST /login with redirect: 'manual' — capture the steadfast_courier_session cookie from the 302 before following it (if fetch auto-follows, those Set-Cookie headers are lost)
  4. Follow the redirect to complete the session
  5. GET /user/consignment/getbyphone/{phone} — send both cookies plus X-XSRF-TOKEN set to the URL-decoded value of the XSRF cookie (Laravel writes it encoded but expects it decoded in the header)

Sessions are cached in memory. If a request returns HTML or a 401/419, the session is refreshed and the request is retried once before trying the next credential.

Only unreachable hosts trigger failover. A rejected password fails immediately, so bad credentials never burn the login throttle across every mirror.


Using with Express / Next.js

The singleton client keeps sessions alive between requests, so only the first call per credential triggers a login:

// Express
app.get('/check/:phone', async (req, res) => {
  try {
    res.json(await checkPhone(req.params.phone));
  } catch (err) {
    res.status(502).json({ error: err.message, failures: err.failures });
  }
});

// Next.js App Router (Node runtime)
export async function GET(req) {
  const phone = new URL(req.url).searchParams.get('phone');
  if (!phone) return Response.json({ error: 'phone required' }, { status: 400 });
  try {
    return Response.json(await checkPhone(phone));
  } catch (err) {
    return Response.json({ error: err.message }, { status: 502 });
  }
}

Troubleshooting

AllCredentialsFailedError: All N credential(s) failed Check err.failures for per-account details. Common causes: wrong password, account locked, or rate limiting. Add more accounts with fraudjs add-credential.

ServiceUnavailableError: No Steadfast host was reachable No configured host served the login page. Run npx fraudjs doctor to see the status of each. If Steadfast has moved to a new origin, point FRAUDJS_BASE_URL at it:

FRAUDJS_BASE_URL=https://new-host.example npx fraudjs check 01700000000

AuthError: Could not extract CSRF token from … The host answered 200 but the login form markup changed. Open the URL in the error message and check for a <meta name="csrf-token"> tag or a hidden _token input.

AuthError: Login failed … — check email/password Credentials were rejected. Verify by logging in to https://steadfast.com.bd manually.

RateLimitError: Too many login attempts Steadfast throttles /login to roughly 10 attempts per window. Wait it out (err.retryAfterSeconds) rather than retrying in a loop.

Failed to decrypt credentials — wrong FRAUDJS_SECRET or corrupted store The FRAUDJS_SECRET doesn't match the one used when the credentials were originally stored. Restore the original secret, or remove and re-add credentials.

Response always shows zeros Phone number likely has no orders in Steadfast. Check result.consignment for the raw API payload.


Tests

npm test

Uses Node's built-in node:test — no extra dependencies. All tests mock globalThis.fetch, no network needed.


License

MIT

Note: This package automates access to the Steadfast merchant portal. You're responsible for complying with their terms. Use only accounts you own and authorise.