Node.js integration
A complete, production-shaped integration between your backend and RideWiseCore.
This guide builds a small module for your Node.js backend that:
- Keeps one authenticated socket to RideWiseCore.
- Forwards agent locations from your agent app.
- Requests matches for passengers and waits for the result.
- Relays offers to agents and their answers back.
- Updates agent status around trips.
It uses TypeScript, socket.io-client and the built-in fetch. The pieces that talk to your apps (notifyAgent, notifyPassenger) are stubs to replace with your own push or socket layer.
npm install socket.io-client1. Typed client
Start with the event types from the Real-time reference. Save them as rwc-events.ts, then create the client:
import { io } from "socket.io-client";
import type { Match, RwcSocket } from "./rwc-events";
const RWC_API_URL = process.env.RWC_API_URL!;
const RWC_API_KEY = process.env.RWC_API_KEY!;
export const rwc: RwcSocket = io(RWC_API_URL, {
auth: { apiKey: RWC_API_KEY },
transports: ["websocket"],
});
rwc.on("connect", () => console.info("[rwc] connected"));
rwc.on("disconnect", (reason) => console.warn("[rwc] disconnected:", reason));
rwc.on("connect_error", (err) => console.error("[rwc] connect_error:", err.message));
class RwcError extends Error {
constructor(
public statusCode: number,
message: string,
) {
super(message);
}
}
async function rwcFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`${RWC_API_URL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${RWC_API_KEY}`,
"Content-Type": "application/json",
...init.headers,
},
});
if (res.status === 204) return undefined as T;
const body = await res.json();
if (!res.ok) throw new RwcError(res.status, body.error?.message ?? res.statusText);
return body as T;
}
export const rwcApi = {
createMatch: (input: { pickup: { lat: number; lng: number }; vehicleType: string; radiusMeters?: number }) =>
rwcFetch<Match>("/matches", { method: "POST", body: JSON.stringify(input) }),
getMatch: (matchId: string) => rwcFetch<Match>(`/matches/${matchId}`),
cancelMatch: (matchId: string) => rwcFetch<Match>(`/matches/${matchId}/cancel`, { method: "POST" }),
};2. Forward agent locations
Your agent app already sends its GPS position to your backend. Forward each update, with the agent's real status:
import { rwc } from "./rwc-client";
import type { AgentStatus } from "./rwc-events";
// Your own source of truth for whether an agent is on a trip.
const agentStatus = new Map<string, AgentStatus>();
export function onAgentGps(agentId: string, gps: { lat: number; lng: number; heading?: number; speed?: number }, vehicleType: string) {
rwc.emit(
"agent:location",
{
agentId,
...gps,
vehicleType,
status: agentStatus.get(agentId) ?? "available", // never omit, or it resets to available
},
(ack) => {
if (!ack.ok) console.warn(`[rwc] location rejected for ${agentId}: ${ack.error}`);
},
);
}
export function setAgentStatus(agentId: string, status: AgentStatus) {
agentStatus.set(agentId, status);
}
export function onAgentSignOff(agentId: string, lastGps: { lat: number; lng: number }, vehicleType: string) {
setAgentStatus(agentId, "offline");
rwc.emit("agent:location", { agentId, ...lastGps, vehicleType, status: "offline" });
}3. Request a match and wait for the result
POST /matches returns straight away. The result arrives later on match:update. This helper turns that into a promise:
import { rwc, rwcApi } from "./rwc-client";
import type { Match } from "./rwc-events";
const pending = new Map<string, (match: Match) => void>();
rwc.on("match:update", (match) => {
pending.get(match.id)?.(match);
pending.delete(match.id);
});
/** Resolves with the final match: matched, unmatched or cancelled. */
export async function findAgent(pickup: { lat: number; lng: number }, vehicleType: string): Promise<Match> {
const match = await rwcApi.createMatch({ pickup, vehicleType });
// No agent at all: the match is already final.
if (match.status !== "searching") return match;
return new Promise<Match>((resolve) => {
pending.set(match.id, resolve);
// Safety net: if the socket dropped and we missed the update, poll.
setTimeout(async () => {
if (!pending.has(match.id)) return;
const latest = await rwcApi.getMatch(match.id);
if (latest.status !== "searching") {
pending.delete(match.id);
resolve(latest);
}
}, 120_000);
});
}Use it in your ride-request handler:
const result = await findAgent({ lat: 40.713, lng: -74.0055 }, "sedan");
if (result.status === "matched") {
setAgentStatus(result.matchedAgentId!, "busy");
await createTripInYourDb({ agentId: result.matchedAgentId!, rwcMatchId: result.id });
notifyPassenger("Agent on the way");
} else {
notifyPassenger("No agents available right now");
}4. Relay offers and answers
import { rwc } from "./rwc-client";
rwc.on("ride:offer", (offer) => {
// If you run several backend instances, make sure only one delivers each offer.
notifyAgent(offer.agentId, {
type: "ride_offer",
matchId: offer.matchId,
pickup: offer.pickup,
// Leave headroom for network latency.
respondWithinMs: offer.expiresInMs - 2_000,
});
});
/** Call this when your agent app sends back Accept/Decline. */
export function onAgentAnswer(agentId: string, matchId: string, response: "accept" | "decline") {
rwc.emit("ride:respond", { matchId, agentId, response }, (ack) => {
if (!ack.ok) {
// Expired, cancelled, or already resolved. Tell the agent the offer is gone.
notifyAgent(agentId, { type: "offer_withdrawn", matchId });
}
});
}5. Finish the trip
RideWiseCore doesn't know when a trip ends. When your trip completes or is cancelled after matching, make the agent available again:
export function onTripEnded(agentId: string) {
setAgentStatus(agentId, "available");
// The next GPS update carries the new status automatically.
}6. Passenger gives up while searching
export async function onPassengerCancelled(matchId: string) {
try {
await rwcApi.cancelMatch(matchId);
} catch (err) {
// 409: already finished or mid-transition. Re-read to see what happened.
const latest = await rwcApi.getMatch(matchId);
if (latest.status === "matched") {
// An agent accepted at the last moment. Cancel the trip in your system
// and release the agent.
onTripEnded(latest.matchedAgentId!);
}
}
}Production checklist
- API key is stored in a secret manager and never sent to clients.
- Every
agent:locationincludes an explicitstatus. - Agent locations are sent every 3–10 s while online, and
offlineis sent on sign-off. - Agent status returns to
availableafter every trip. -
ride:offerhandling doesn't depend on thePOST /matchesresponse arriving first. - Each offer is delivered to the agent once, even with several backend instances.
-
ride:respondacknowledgements withok: falseare shown to the agent. -
401,402and403responses and socketconnect_errors alert your on-call team, because they mean dispatch is down. - You have a documented key rotation procedure.