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

@kacoon/efihub-client

v1.2.2

Published

JavaScript/TypeScript SDK for EFIHUB API using OAuth2 Client Credentials Flow

Readme


Introduction

EFIHUB client for Node.js / TypeScript that authenticates via OAuth 2.0 Client Credentials and exposes:

  • HTTP client — GET / POST / PUT / DELETE / postMultipart, with refresh token handling
  • Storage module — upload files and manage them on EFIHUB storage
  • WebSocket module — dispatch real-time events to channels
  • SSO module — generate an authorization URL and fetch user profile after callback
  • WhatsApp module — manage agents, send messages, retrieve messages

Server-side only. Never expose your clientSecret in a browser or public environment.


Table of Contents


Installation

npm install @kacoon/efihub-client
# or
yarn add @kacoon/efihub-client
# or
pnpm add @kacoon/efihub-client

Environment Variables

EFIHUB_CLIENT_ID=your_client_id
EFIHUB_CLIENT_SECRET=your_client_secret

Authentication

The client uses the OAuth 2.0 Client Credentials flow automatically:

  • Access token is fetched on the first request and cached until expiry
  • On a 401 response, the cache is cleared and the request is retried once
  • No manual token management needed
import { EfihubClient } from "@kacoon/efihub-client";

const efihub = new EfihubClient({
  clientId: process.env.EFIHUB_CLIENT_ID!,
  clientSecret: process.env.EFIHUB_CLIENT_SECRET!,
});

HTTP Client

Basic Usage

// GET with query params
const res = await efihub.get("/user", { params: { page: 1 } });

// POST JSON
const res = await efihub.post("/orders", { sku: "ABC", qty: 2 });

// PUT
const res = await efihub.put("/orders/123", { qty: 3 });

// DELETE
const res = await efihub.delete("/orders/123");

TypeScript generics are supported:

interface Order {
  id: number;
  qty: number;
}
const resp = await efihub.get<Order>("/orders/123");
const order = resp.data; // typed as Order

Multipart Upload

Use postMultipart to send multipart/form-data alongside file attachments to any endpoint:

const res = await efihub.postMultipart(
  "/documents",
  { type: "invoice" }, // plain fields
  { file: "/path/to/invoice.pdf" }, // files (field name → local path)
);

Modules

All modules are accessed via lazy-initialized accessors on EfihubClient (e.g. efihub.storage(), efihub.whatsapp()).


Storage Module

Upload files and manage them on the EFIHUB storage service.

Methods

| Method | Return | Description | | ------------------------------------ | -------------------------- | ------------------------------------------------------- | | upload(input, destPath, filename?) | Promise<string \| false> | Upload a file; returns public URL or false on failure | | exists(filePath) | Promise<boolean> | Check whether a file exists | | size(filePath) | Promise<number \| null> | File size in bytes, or null on failure | | delete(filePath) | Promise<boolean> | Delete a file; returns true on success |

upload() accepts a string file path, Buffer, or Readable stream as input. End destPath with / to let the server auto-generate the filename.

Usage

// Upload a local file
const url = await efihub
  .storage()
  .upload("/path/to/photo.jpg", "uploads/2024/06/");
if (url === false) {
  // handle upload failure
}
console.log(url); // https://...

// Other helpers
const exists = await efihub.storage().exists("uploads/photo.jpg"); // boolean
const bytes = await efihub.storage().size("uploads/photo.jpg"); // number | null
const ok = await efihub.storage().delete("uploads/photo.jpg"); // boolean

Endpoints

  • POST /storage/upload
  • GET /storage/url
  • GET /storage/size
  • GET /storage/exists
  • DELETE /storage/delete

WebSocket Module

Dispatch real-time events to channels from a server-side listener or job.

Methods

| Method | Return | Description | | ------------------------------------- | ------------------ | ----------------------------------- | | dispatch(channel, event, data?) | Promise<boolean> | Flat-arg style (Laravel-compatible) | | dispatch({ channel, event, data? }) | Promise<boolean> | Object-payload style |

Usage

// Flat-arg style (matches Laravel API)
const ok = await efihub.socket().dispatch("orders:updates", "OrderUpdated", {
  order_id: 123,
  status: "updated",
});

// Object style
const ok = await efihub.socket().dispatch({
  channel: "orders:updates",
  event: "OrderUpdated",
  data: { order_id: 123 },
});

if (!ok) {
  // handle failure (log, retry, etc.)
}

Endpoint

  • POST /websocket/dispatch — body: { channel, event, data }

SSO Module

Centralized login flow: generate an authorization URL, redirect the user, then exchange the redirect_token for user profile data.

Methods

| Method | Return | Description | | ------------------------- | --------------------------------------- | ----------------------------------------------------------------- | | login() | Promise<string \| false> | Returns the authorization URL or false on failure | | userData(redirectToken) | Promise<Record<string, any> \| false> | Exchanges the token for user info; returns user object or false |

Usage

Step 1 — Redirect the user to EFIHUB login:

const authUrl = await efihub.sso().login();
if (authUrl === false) {
  // log error and abort
}
// e.g. res.redirect(authUrl)  (Express) or return redirect(authUrl)  (Next.js)

Step 2 — Handle the callback and fetch user data:

const redirectToken = req.query.redirect_token as string;
const user = await efihub.sso().userData(redirectToken);

if (user === false) {
  // invalid token or request failed
} else {
  console.log(user.email, user.name);
}

Security note: bind a state/nonce to the session before redirecting to prevent CSRF attacks on the callback.

Endpoints

  • POST /sso/authorize
  • GET /sso/user

WhatsApp Module

Manage WhatsApp agents and send text messages or file attachments to individual recipients or groups.

Methods

Agent management

| Method | Return | Description | | ------------------------------------- | ------------------------- | -------------------------------------------------- | | agents() | Promise<any[]> | List all registered agents/sessions | | agentQR(agentCode) | Promise<string \| null> | QR code as data:image/png;base64,... or null | | agentStatus(agentCode) | Promise<string \| null> | 'connected' | 'disconnected' | null | | checkPhoneNumber(agentCode, number) | Promise<boolean> | true if the number is a registered WhatsApp user |

Messaging

| Method | Return | Description | | ------------------------------------------------------------------------- | ------------------ | -------------------------------------------- | | sendMessage(sender, to, message, ref_id?, ref_url?) | Promise<boolean> | Send a text message to a single recipient | | sendGroupMessage(sender, to, message, ref_id?, ref_url?) | Promise<boolean> | Send a text message to a group | | sendAttachment(sender, to, message, attachment, ref_id?, ref_url?) | Promise<boolean> | Send message + file(s) to a single recipient | | sendGroupAttachment(sender, to, message, attachment, ref_id?, ref_url?) | Promise<boolean> | Send message + file(s) to a group | | getMessages(agentCode, phone, limit?) | Promise<any[]> | Retrieve recent messages (default limit: 10) |

All send* methods return true on HTTP 2xx, false otherwise.

Phone number normalization

All send methods automatically normalize phone numbers to the international format (62xxxxxxxxxx for Indonesia):

| Input | Normalized | | --------------- | -------------- | | +628109998877 | 628109998877 | | 08109998877 | 628109998877 | | 628109998877 | 628109998877 |

Attachment formats

The attachment parameter of sendAttachment / sendGroupAttachment accepts a single spec or an array of specs:

| Format | Example | | --------------------- | --------------------------------------------------------------------------------------- | | Path string | "/path/to/file.pdf" | | Path with custom name | { path: "/path/to/file.pdf", filename: "custom.pdf" } | | Raw contents | { contents: buffer, filename: "report.csv", headers: { "Content-Type": "text/csv" } } | | Multiple files | ["/path/a.pdf", "/path/b.pdf"] |

Usage

// List agents
const agents = await efihub.whatsapp().agents();

// QR code (for display / scanning)
const qr = await efihub.whatsapp().agentQR("AGENT1");
// qr === 'data:image/png;base64,...' or null

// Connection status
const status = await efihub.whatsapp().agentStatus("AGENT1");
// 'connected' | 'disconnected' | null

// Validate a number before sending
const valid = await efihub
  .whatsapp()
  .checkPhoneNumber("AGENT1", "+628109998877");
if (!valid) {
  /* number not on WhatsApp */
}

// Send text message (number auto-normalized)
const ok = await efihub.whatsapp().sendMessage(
  "AGENT1",
  "+628109998877",
  "Halo! Test pesan.",
  "order-9988", // optional ref_id
  "https://yourapp.com/orders/9988", // optional ref_url
);

// Send to a group
await efihub
  .whatsapp()
  .sendGroupMessage("AGENT1", "group-abc123", "Hello group!");

// Send attachment — single file path
await efihub
  .whatsapp()
  .sendAttachment(
    "AGENT1",
    "+628109998877",
    "Berikut invoice kamu",
    "/path/to/invoice.pdf",
  );

// Send attachment — raw buffer with custom name
await efihub
  .whatsapp()
  .sendAttachment("AGENT1", "+628109998877", "Report CSV", {
    contents: csvBuffer,
    filename: "report.csv",
    headers: { "Content-Type": "text/csv" },
  });

// Send multiple attachments to a group
await efihub
  .whatsapp()
  .sendGroupAttachment("AGENT1", "group-abc123", "Semua dokumen", [
    "/path/doc1.pdf",
    "/path/doc2.pdf",
  ]);

// Retrieve recent messages
const messages = await efihub
  .whatsapp()
  .getMessages("AGENT1", "+628109998877", 20);
for (const msg of messages) {
  console.log(msg.from, msg.message);
}

Endpoints

| Method | Endpoint | | ----------------------- | ---------------------------------------------------- | | agents() | GET /whatsapp/sessions | | agentQR() | GET /whatsapp/sessions/qrcode/{agentCode} | | agentStatus() | GET /whatsapp/sessions/status/{agentCode} | | checkPhoneNumber() | GET /whatsapp/user/exists/{agentCode}/{number} | | sendMessage() | POST /whatsapp/message | | sendGroupMessage() | POST /whatsapp/message/group | | sendAttachment() | POST /whatsapp/message/attachment | | sendGroupAttachment() | POST /whatsapp/message/group/attachment | | getMessages() | GET /whatsapp/messages/{agentCode}/{phone}/{limit} |


License & Author

MIT © Imam Nurcholis. See LICENSE.

  • Author: https://github.com/imamnc
  • EFIHUB: https://efihub.morefurniture.id