Architecture of BaseClient and Its Services in LINEJS: Core Design Explained
LINEJS is built around a single BaseClient class that wires together low-level utilities and exposes typed service objects to implement the LINE API endpoints.
The LINEJS library (evex-dev/linejs) provides a TypeScript SDK for the LINE platform. Understanding the BaseClient and its services architecture is essential for extending the client or debugging request flows, as it separates cross-cutting concerns like encryption and polling from domain-specific API implementations.
BaseClient as the Core Orchestrator
Entry Point and Event System
The nucleus of the SDK is defined in packages/linejs/base/core/mod.ts at line 101. Here, the BaseClient class extends TypedEventEmitter to emit high-level events such as log, update:authtoken, and various LINE event streams. Because services hold a reference to the client, any service can raise events that bubble up to the user-facing Client wrapper (see Client.listen in packages/linejs/client/client.ts).
Constructor Responsibilities
The constructor (lines 152-160) initializes deviceDetails and endpoint configurations. Between lines 176-186, it instantiates eight typed services: auth, call, channel, liff, livetalk, relation, square, and talk. It also initializes utility modules including request, e2ee (end-to-end encryption), obs, timeline, poll, push, and storage.
For stateful request tracking, the getReqseq method (lines 204-218) generates per-service sequence numbers and persists them via the storage abstraction. The client supports pluggable HTTP stacks through a private #customFetch field; the public fetch wrapper (lines 220-232) forwards to either the injected fetch or globalThis.fetch.
Service Abstraction Layer
The BaseService Contract
All services share a minimal contract defined in packages/linejs/base/service/types.ts (lines 1-9):
export interface BaseService {
client: BaseClient;
protocolType: ProtocolKey;
requestPath: string;
errorName: string;
}
Each concrete service stores a reference to the owning BaseClient and provides typed methods that wrap the low-level RequestClient.request call.
AuthService Implementation
AuthService in packages/linejs/base/service/auth/mod.ts (line 8) implements the contract with protocolType set to 4, requestPath set to "/AS4", and errorName set to "AuthServiceError".
Key methods include:
tryRefreshToken(lines 20-33): Loads the stored refresh token, calls the low-levelrefreshmethod, updatesauthToken, and emitsupdate:authtoken.refresh(lines 36-45): Makes the actual request to"/EXT/auth/tokenrefresh/v1"usingLINEStruct.refresh_args.openSession(lines 84-94): Opens a new LINE session with the authentication servers.
TalkService for Messaging
TalkService at packages/linejs/base/service/talk/mod.ts (line 10) handles messaging operations. The sync method (lines 30-62) fetches event batches from the "/SYNC4" endpoint. The sendMessage method (lines 80-71) builds a LINEStruct.sendMessage_args payload, transparently handles E2EE encryption, and retries automatically on encryption errors.
Request Flow and Event Handling
The interaction flow follows four stages:
- Construction:
new BaseClient({device, version, ...})builds core utilities and all service objects. - Login:
client.loginProcess.login(...)(handled inpackages/linejs/base/login/mod.ts) populatesclient.authTokenand initializes the push connection. - API Call: When a consumer calls
client.talk.sendMessage(...), the call delegates to theTalkServiceinstance. The service builds a Thrift request and callsclient.request.request, which uses the fetch wrapper and injects required headers (auth token, device info, etc.). - Event Listening:
client.listen({talk:true, square:true})creates aPollingobject viaclient.createPolling(). ThePollingclass (inpackages/linejs/base/polling/mod.ts) internally callsclient.talk.syncandclient.square.fetchMyEvents, emitting high-level events that the client re-dispatches to user code.
Extensibility Points
Pluggable Storage and Fetch
Developers can inject a custom storage implementation (defaulting to MemoryStorage) to persist auth tokens and request sequences. A custom fetch implementation can also be provided for request signing, proxy support, or testing environments.
Adding New Services
To extend the API surface, implement the BaseService interface, inject the new service in BaseClient’s constructor alongside the existing services, and expose it via the public client wrapper.
Code Examples
Basic Client Setup and Messaging
import { BaseClient } from "https://deno.land/x/linejs@latest/packages/linejs/base/core/mod.ts";
const client = new BaseClient({
device: "iOS",
version: "15.0",
});
await client.loginProcess.login({
// login credentials (access token, etc.)
});
await client.talk.sendMessage({
to: "u1234567890abcdef1234567890abcdef",
text: "Hello from LINEJS!",
});
Manual Token Refresh
try {
await client.auth.tryRefreshToken();
console.log("Token refreshed, new token:", client.authToken);
} catch (e) {
console.error("Refresh failed:", e);
}
Listening for Events
const abort = new AbortController();
client.listen({
talk: true,
square: true,
signal: abort.signal,
});
client.on("message", (msg) => {
console.log("New talk message:", msg.raw.text);
});
client.on("square:message", (msg) => {
console.log("New square message:", msg.raw.text);
});
// Stop listening later:
// abort.abort();
Summary
BaseClientinpackages/linejs/base/core/mod.tsacts as the nucleus, extendingTypedEventEmitterfor event propagation.- Services implement the
BaseServiceinterface frompackages/linejs/base/service/types.ts, encapsulating protocol types and request paths. - AuthService and TalkService demonstrate the pattern: store client reference, define protocol metadata, and wrap Thrift calls.
- Request flow moves from service methods →
RequestClient→ pluggablefetchwith automatic header injection. - Extensibility is supported via custom storage, custom fetch, and the ability to add new services following the established interface.
Frequently Asked Questions
How does BaseClient handle authentication token refreshes?
According to the evex-dev/linejs source code, BaseClient delegates token management to AuthService in packages/linejs/base/service/auth/mod.ts. The tryRefreshToken method (lines 20-33) retrieves the stored refresh token, calls the refresh endpoint, updates the client's authToken property, and emits an update:authtoken event that other services can observe.
What is the relationship between BaseClient and the user-facing Client class?
BaseClient provides the core infrastructure and service objects, while Client in packages/linejs/client/client.ts wraps it with high-level conveniences. Client exposes methods like listen() that internally use BaseClient.createPolling() and re-dispatch events from BaseClient's event emitter to user code with richer context.
Can I use a custom HTTP client or storage backend with LINEJS?
Yes. The BaseClient constructor accepts a custom fetch implementation via the #customFetch private field, which the public fetch wrapper (lines 220-232) prioritizes over globalThis.fetch. Similarly, you can inject any storage implementation matching the interface used by getReqseq (lines 204-218) to replace the default MemoryStorage.
How are real-time events fetched in LINEJS?
Real-time events use a polling architecture. When you call client.listen(), the client creates a Polling instance (in packages/linejs/base/polling/mod.ts) that periodically calls client.talk.sync for personal chats and client.square.fetchMyEvents for OpenChat rooms. These methods return batches of events that the SDK emits as high-level JavaScript events.
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 →