gmessages
v0.1.2
Published
A TypeScript client for Google Messages for web, with per-field provenance verified against Google's own descriptors. The protocol layer of Cast.
Maintainers
Readme
gmessages
A TypeScript client for Google Messages for web, whose protocol shapes are verified against Google's own descriptors rather than inferred.
import { connect, sendMessage, listConversations, messagesOf, nodeFileStore, GOOGLE_ENDPOINTS, GOOGLE_WEB_API_KEY } from "gmessages";
const client = await connect({
endpoints: GOOGLE_ENDPOINTS,
apiKey: GOOGLE_WEB_API_KEY, // the relay requires it; it is the public web-client key, not a secret
...(await nodeFileStore("./session.json")), // session blob in, rotations persisted; or wire your own store
fetchImpl: fetch,
onEvent: (event) => {
if (event.kind === "push") for (const m of messagesOf(event.update)) console.log(m.text);
},
});
const conversations = await listConversations(client.operations, { count: 25 });
for (const c of conversations) console.log(c.conversationId, c.participants);
await sendMessage(client.operations, { conversationId: "…", text: "hello", participantId: "…" });session.json comes from pairing with the phone — docs/QUICKSTART.md walks from a
signed-in browser to a first received message. Read docs/SECURITY.md before storing
that file anywhere: it holds account-wide credentials.
What it does
- Receive — holds the long-poll stream open and decodes every push into a typed message, with text, attachments, reactions and delivery status.
- Read — conversations (inbox and archive, paged), message history, contacts, thumbnails.
- Pair — from an account's cookies, with a person confirming a code on the handset. No browser export needed.
- Send — text and media, into an existing thread or a number you have never messaged.
- Act — reactions, mark read, mark unread, typing, archive, unarchive, block, unblock, delete.
- Media — encrypted upload and download, both directions, chunked AES-GCM as the client does it.
An attachment arrives with no handle until it is resolved (
getFullSizeMedia), which is a request, not a wait — and it completes a moment after it returns rather than in its reply. - Delivery — reports
sending/accepted/delivered/read, and tells you when a thread can never report delivery at all.
What it does not do
Read this part before adopting.
- Pairing needs a person at the phone.
pairFromCookiestakes a signed-in account's cookies and returns session keys, and it has done so against a live account. It cannot be unattended: the protocol shows a code that someone has to compare on the handset and confirm, which is the point of the design rather than a gap in this one.convertLegacySessionremains the path for a session a browser already established. See docs/PAIRING.md. - SMS cannot report delivery. Not a gap here: SMS threads stop at
acceptedand no further status arrives. RCS threads reportdeliveredandread.reportsDelivery()tells you which you have. - Roughly twenty protocol actions are unimplemented — stickers, RCS group management, settings. Each would be an unverified shape, so they are absent rather than guessed.
- Media send is proven over MMS, not RCS. This build has uploaded a file and had the relay deliver it on an SMS thread. RCS media has not been exercised, and it is not a safe extrapolation: Google's own client uses a second upload endpoint for RCS and falls back to this one only when RCS degrades to MMS. This library only ever uses the endpoint MMS uses.
- Tested against one account. Broad in capability, narrow in population.
- Not run unattended for long. Reconnect and cookie rotation are implemented and not yet proven over days.
- Google's terms may prohibit automated access. Your account, your call. This library never attempts to circumvent a sign-in check, and acquiring cookies is a manual step for that reason.
Verification
The protocol shapes are not asserted, they are checked. Google's client ships a descriptor-driven binary serializer; the test suite loads it, plants one value in one field at a time, and reads back the tag Google emits — so every field number and wire type comes from Google's compiler rather than from anyone's reading of minified source.
descriptor oracle 298 messages, 1269 fields, 0 mismatched, 0 unchecked
codec differential 30,218 slots, 0 failedThose suites need Google's bundle, which is not redistributable. They are excluded from collection when it
is absent, so a fresh clone runs the offline suite and reports the rest as skipped rather than silently
passing. capture/MANIFEST.json pins the exact bundle — URL, versions, SHA-256 per module — so the run can
be reproduced. See PROVENANCE.md.
Where this came from
This client is the protocol layer of Cast.
Cast is a self-hosted harness for multi-user Claude Code agents that run as isolated container processes, with per-channel identity, an access-control layer, and scheduling. An agent that can send messages needs more than a protocol client: it needs to know who is allowed to message whom, which account it speaks as, and how a human authorises an action. This library deliberately supplies none of that — it will send wherever it is told — and Cast is the layer that decides.
That division is why the API is shaped the way it is, and why the capabilities are free functions over a dispatcher rather than methods on an object. docs/AGENTS.md covers what an agent needs beyond the wire, and how the two fit together.
The protocol layer is independently useful and independently tested, which is why it is published on its own. Nothing here depends on Cast.
Install
pnpm add gmessagesNode 20 or newer. MIT.
Contributing
pnpm test runs the suite. The oracle suites need Google's bundle, so a fresh clone reports them skipped
rather than passed. pnpm run typecheck is separate and is part of prepublishOnly.
Comments here are for load-bearing constraints, the kind a reader would otherwise simplify away and break. They ship with the build — the declaration files carry them into a consumer's editor, so a comment explaining where a shape came from is part of the API surface. If a change needs justifying, the test that would fail without it is still the durable form.
