Client vs Base Client in LINEJS: Understanding the Two-Layer Architecture
The BaseClient is the low-level engine that handles raw Thrift RPCs, authentication, and network protocols, while the Client is a high-level façade that wraps the base layer with convenient methods, typed events, and domain objects like Chat and User.
LINEJS provides two distinct layers for interacting with the LINE messaging platform. Understanding the difference between client and base client modules in LINEJS is essential for choosing the right abstraction level for your application. The base layer (@evex/linejs/base) implements the core protocol engine, while the client layer (@evex/linejs/client) delivers developer-friendly APIs and event handling.
What Is the Base Client?
The BaseClient (@evex/linejs/base) serves as the core engine of the library. Located in packages/linejs/base/core/mod.ts, it manages low-level network handling, authentication flows, generated Thrift services, storage, push connections, polling, and E2EE encryption.
- It exposes a thin API that mirrors LINE’s internal RPCs directly through service objects like
base.talk,base.square, andbase.auth. - It handles request sequencing, retries, and raw protocol details.
- It provides underlying push streams via
base.push.opStreamandbase.push.sqStreambut does not emit high-level events.
Use the BaseClient when you need direct access to specific services, want to build custom wrappers, or require fine-grained control over the login flow and network behavior.
What Is the High-Level Client?
The Client (@evex/linejs/client) provides a convenient façade built on top of a BaseClient. Defined in packages/linejs/client/client.ts, it wires the base client to high-level domain objects and emits strongly-typed events.
- It aggregates common helper methods like
fetchJoinedChats(),fetchJoinedSquares(), andgetUser(). - It extends
TypedEventEmitterto provide developer-friendly events such asmessage,event, andsquare:messagewith parsed objects likeTalkMessageandSquareMessage. - It stores the underlying
BaseClientinstance in a read-onlybaseproperty, allowing access to low-level operations when needed.
This is the typical entry point for most applications, obtained through login helpers like loginWithPassword, loginWithQR, or loginWithAuthToken.
Key Architectural Differences
Construction and Initialization
The BaseClient is instantiated directly with low-level options. According to the source code in packages/linejs/base/core/mod.ts (lines 101-106), its constructor accepts device configuration, endpoints, optional custom fetch implementations, and storage handlers.
import { BaseClient } from "https://deno.land/x/linejs/base/mod.ts";
const base = new BaseClient({
device: "Android",
version: "13.0",
});
In contrast, the Client constructor (lines 35-38 in packages/linejs/client/client.ts) receives an already-initialized BaseClient created by login helpers. The login utilities in packages/linejs/client/login.ts perform authentication steps and return a ready-to-use Client instance.
Responsibility Split
- BaseClient: Knows how to talk to LINE. It builds Thrift requests, manages connection persistence, handles retries, and provides raw service access.
- Client: Knows what developers typically want. It wraps raw services into semantic methods and translates protocol buffers into usable JavaScript objects.
Event Model
The BaseClient only exposes raw push streams. The Client class implements the event-driven interface through its listen() method (lines 47-98 in packages/linejs/client/client.ts), which processes raw LINE operations and emits processed domain events.
Code Examples
Using the High-Level Client (Recommended)
The loginWithPassword helper returns a Client that wraps a BaseClient and provides high-level methods and events.
import { loginWithPassword } from "https://deno.land/x/linejs/client/login.ts";
const client = await loginWithPassword(
{
email: "you@example.com",
password: "superSecret",
onPincodeRequest: (pin) => console.log("Enter PIN:", pin),
},
{
device: "iOS",
version: "15.0",
},
);
// Listen to incoming chat messages
client.listen({ talk: true, square: true }).on("message", (msg) => {
console.log(`New talk message from ${msg.senderMid}: ${msg.text}`);
});
// Fetch all joined chats
const chats = await client.fetchJoinedChats();
console.log(`You have ${chats.length} chats.`);
Using the Low-Level BaseClient Directly
Interact with raw services when you need direct RPC access without event handling or object wrappers.
import { BaseClient } from "https://deno.land/x/linejs/base/mod.ts";
const base = new BaseClient({
device: "Android",
version: "13.0",
});
// Manual authentication
base.authToken = Deno.env.get("LINE_AUTH_TOKEN")!;
// Direct RPC call
const chat = await base.talk.getChat({
chatMid: "c1234567890abcdef1234567890abcd",
withInvitees: true,
withMembers: true,
});
console.log("Chat title:", chat.title);
Mixing Both Layers
Access the underlying BaseClient through the base property when you need low-level operations alongside high-level convenience.
import { loginWithQR } from "https://deno.land/x/linejs/client/login.ts";
const client = await loginWithQR(
{
onReceiveQRUrl: async (url) => console.log("Scan this QR:", url),
onPincodeRequest: (pin) => console.log("Enter PIN:", pin),
},
{ device: "iOS" },
);
// Use raw request via underlying BaseClient
const raw = await client.base.request.post("/v1/users/@me/profile", {
method: "GET",
});
console.log("Raw profile JSON:", await raw.json());
Summary
- BaseClient (
@evex/linejs/base) is the protocol engine handling Thrift services, authentication, and raw network logic inpackages/linejs/base/core/mod.ts. - Client (
@evex/linejs/client) is the developer-friendly interface providing typed events, domain objects, and helper methods inpackages/linejs/client/client.ts. - Login helpers in
packages/linejs/client/login.tsinstantiateBaseClient, perform authentication, and return a configuredClient. - The
Clientstores theBaseClientin itsbaseproperty, allowing hybrid usage patterns. - Choose
BaseClientfor protocol-level control andClientfor application development.
Frequently Asked Questions
Can I use BaseClient without the high-level Client?
Yes. You can instantiate BaseClient directly from packages/linejs/base/core/mod.ts and manage authentication and service calls manually. This is useful when building custom frameworks or when you need to minimize bundle size by excluding the high-level event system.
How do I access low-level methods from the high-level Client?
The Client class exposes the underlying BaseClient through its read-only base property. As shown in packages/linejs/client/client.ts (lines 35-38), this allows you to call raw RPCs like client.base.talk.getChat() while still using high-level features for other operations.
Does the BaseClient emit events?
No. The BaseClient only provides raw push streams (base.push.opStream, base.push.sqStream). High-level typed events are implemented exclusively in the Client class through its listen() method, which processes raw operations and emits parsed objects like TalkMessage and SquareMessage.
Which module should I import for a typical chat bot?
Import from @evex/linejs/client using the login helpers (loginWithPassword, loginWithQR) defined in packages/linejs/client/login.ts. This provides the Client instance with event emitters and helper methods, offering the most productive API for building chat bots without managing raw Thrift protocols.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →