@norialabs/comm
v0.1.2
Published
Reusable TypeScript/JavaScript SDK for Onfon SMS and Meta WhatsApp messaging.
Downloads
189
Maintainers
Readme
@norialabs/comm
TypeScript/JavaScript SDK for Onfon SMS and Meta WhatsApp messaging.
Node >=20 is required.
Designed for Node.js services, workers, and serverless messaging flows.
Install
npm install @norialabs/commWhat This Package Gives You
- one package for SMS and WhatsApp messaging workflows
- Onfon SMS send, balance, groups, templates, and delivery report parsing
- Meta WhatsApp text, template, media, location, contacts, reaction, interactive, catalog, product, product-list, and flow sends
- Meta WhatsApp template management and media upload helpers
- reusable fetch-based transport with retries, hooks, and typed errors
- generic webhook helpers for Meta signature verification and subscription challenge resolution
Quick Start
import { MessagingClient, OnfonSmsGateway, MetaWhatsAppGateway } from "@norialabs/comm";
const messaging = new MessagingClient({
sms: new OnfonSmsGateway({
accessKey: process.env.ONFON_ACCESS_KEY!,
apiKey: process.env.ONFON_API_KEY!,
clientId: process.env.ONFON_CLIENT_ID!,
defaultSenderId: "NORIALABS",
}),
whatsapp: new MetaWhatsAppGateway({
accessToken: process.env.META_WHATSAPP_ACCESS_TOKEN!,
phoneNumberId: process.env.META_WHATSAPP_PHONE_NUMBER_ID!,
whatsappBusinessAccountId: process.env.META_WHATSAPP_WHATSAPP_BUSINESS_ACCOUNT_ID!,
}),
});
await messaging.sms.send({
senderId: "NORIALABS",
messages: [
{
recipient: "254700123456",
text: "Your OTP is 123456",
reference: "otp-1",
},
],
});
await messaging.whatsapp.sendText({
recipient: "254700123456",
text: "Hello from Noria",
});Main Exports
import {
MessagingClient,
OnfonSmsGateway,
MetaWhatsAppGateway,
resolveMetaSubscriptionChallenge,
verifyMetaSignature,
} from "@norialabs/comm";Subpath exports:
import { OnfonSmsGateway } from "@norialabs/comm/sms";
import { MetaWhatsAppGateway } from "@norialabs/comm/whatsapp";
import { resolveMetaSubscriptionChallenge } from "@norialabs/comm/webhooks";SMS
Onfon gateway construction
import { OnfonSmsGateway } from "@norialabs/comm";
const sms = new OnfonSmsGateway({
accessKey: process.env.ONFON_ACCESS_KEY!,
apiKey: process.env.ONFON_API_KEY!,
clientId: process.env.ONFON_CLIENT_ID!,
defaultSenderId: "NORIALABS",
});Environment construction
const sms = OnfonSmsGateway.fromEnv();Supported env vars:
ONFON_ACCESS_KEYONFON_API_KEYONFON_CLIENT_IDONFON_SENDER_IDONFON_BASE_URLONFON_TIMEOUT_SECONDS
Send SMS
const result = await sms.send({
senderId: "NORIALABS",
messages: [
{ recipient: "254700123456", text: "Hello there", reference: "msg-1" },
{ recipient: "254711111111", text: "Hello again", reference: "msg-2" },
],
isUnicode: false,
});Balance, groups, and templates
await sms.getBalance();
await sms.listGroups();
await sms.createGroup({ name: "VIP Customers" });
await sms.updateGroup("group-1", { name: "Priority Customers" });
await sms.deleteGroup("group-1");
await sms.listTemplates();
await sms.createTemplate({ name: "otp", body: "Your OTP is {{1}}" });
await sms.updateTemplate("template-1", { name: "otp", body: "Use code {{1}}" });
await sms.deleteTemplate("template-1");Delivery reports
const report = sms.parseDeliveryReport({
messageId: "abc123",
mobile: "254700123456",
status: "Delivered",
});Meta gateway construction
import { MetaWhatsAppGateway } from "@norialabs/comm";
const whatsapp = new MetaWhatsAppGateway({
accessToken: process.env.META_WHATSAPP_ACCESS_TOKEN!,
phoneNumberId: process.env.META_WHATSAPP_PHONE_NUMBER_ID!,
whatsappBusinessAccountId: process.env.META_WHATSAPP_WHATSAPP_BUSINESS_ACCOUNT_ID!,
appSecret: process.env.META_WHATSAPP_APP_SECRET,
webhookVerifyToken: process.env.META_WHATSAPP_WEBHOOK_VERIFY_TOKEN,
});Environment construction
const whatsapp = MetaWhatsAppGateway.fromEnv();Supported env vars:
META_WHATSAPP_ACCESS_TOKENMETA_WHATSAPP_PHONE_NUMBER_IDMETA_WHATSAPP_WHATSAPP_BUSINESS_ACCOUNT_IDMETA_WHATSAPP_APP_SECRETMETA_WHATSAPP_WEBHOOK_VERIFY_TOKENMETA_WHATSAPP_API_VERSIONMETA_WHATSAPP_BASE_URLMETA_WHATSAPP_TIMEOUT_SECONDS
Send messages
await whatsapp.sendText({
recipient: "254700123456",
text: "Plain text message",
previewUrl: true,
});
await whatsapp.sendTemplate({
recipient: "254700123456",
templateName: "order_update",
languageCode: "en",
components: [
{
type: "body",
parameters: [{ type: "text", value: "NORIA-123" }],
},
],
});
await whatsapp.sendMedia({
recipient: "254700123456",
mediaType: "image",
link: "https://example.com/product.jpg",
caption: "Preview",
});
await whatsapp.sendLocation({
recipient: "254700123456",
latitude: -1.286389,
longitude: 36.817223,
name: "Nairobi Office",
});Interactive, commerce, and flow messages
await whatsapp.sendInteractive({
recipient: "254700123456",
interactiveType: "button",
bodyText: "Choose one",
buttons: [
{ identifier: "yes", title: "Yes" },
{ identifier: "no", title: "No" },
],
});
await whatsapp.sendCatalog({
recipient: "254700123456",
bodyText: "Browse our catalog",
});
await whatsapp.sendProduct({
recipient: "254700123456",
catalogId: "catalog-1",
productRetailerId: "sku-1",
});
await whatsapp.sendProductList({
recipient: "254700123456",
catalogId: "catalog-1",
header: { type: "text", text: "Featured" },
sections: [
{
title: "Top Picks",
productItems: [{ productRetailerId: "sku-1" }],
},
],
});
await whatsapp.sendFlow({
recipient: "254700123456",
flowCta: "Start",
flowId: "flow-1",
flowAction: "navigate",
});Template management
await whatsapp.listTemplates({ limit: 20 });
await whatsapp.getTemplate("tmpl-1");
await whatsapp.createTemplate({
name: "order_update",
language: "en_US",
category: "utility",
components: [
{
type: "body",
text: "Order {{1}} is ready",
},
],
});
await whatsapp.updateTemplate("tmpl-1", {
category: "utility",
});
await whatsapp.deleteTemplate({ templateId: "tmpl-1" });Media helpers
await whatsapp.uploadMedia({
filename: "menu.pdf",
mimeType: "application/pdf",
content: Buffer.from("file-bytes"),
});
await whatsapp.getMedia("media-1");
await whatsapp.deleteMedia("media-1");Parsing delivery and inbound events
const deliveryEvents = whatsapp.parseEvents(metaWebhookPayload);
const inboundMessages = whatsapp.parseInboundMessages(metaWebhookPayload);Webhooks
Resolve Meta subscription challenge
import { resolveMetaSubscriptionChallenge } from "@norialabs/comm/webhooks";
const challenge = resolveMetaSubscriptionChallenge(
{
"hub.mode": "subscribe",
"hub.verify_token": "verify-me",
"hub.challenge": "12345",
},
"verify-me",
);Verify Meta signature
import { requireValidMetaSignature } from "@norialabs/comm/webhooks";
requireValidMetaSignature(rawBody, req.headers["x-hub-signature-256"], appSecret);Parse Onfon delivery reports
import { parseOnfonDeliveryReport } from "@norialabs/comm/webhooks";
const event = parseOnfonDeliveryReport(req.query, smsGateway);Transport customization
Both gateways support:
- custom
fetch timeoutMsdefaultHeadersretryhooks
Example:
const sms = new OnfonSmsGateway({
accessKey: "access-key",
apiKey: "api-key",
clientId: "client-id",
fetch: customFetch,
timeoutMs: 15_000,
retry: {
maxAttempts: 3,
retryMethods: ["GET", "POST"],
retryOnStatuses: [429, 500, 502, 503, 504],
retryOnNetworkError: true,
baseDelayMs: 250,
},
hooks: {
beforeRequest(context) {
context.headers.set("x-trace-id", "trace-123");
},
},
});Errors
Important exported errors:
ConfigurationErrorApiErrorGatewayErrorNetworkErrorTimeoutErrorWebhookVerificationError
Package scope
Implemented today:
- Onfon SMS send, balance, groups, templates, and delivery report parsing
- Meta WhatsApp sends, template management, media helpers, and webhook event parsing
Not implemented today:
- extra SMS gateways
- framework-specific webhook adapters
