How the Thrift Protocol Handles LINE API Requests in LINEJS: A Complete Technical Guide
LINEJS uses Apache Thrift to serialize request arguments into binary payloads, transmits them via HTTP to LINE's private API endpoints, and deserializes responses using custom writeThrift and readThrift utilities managed by the RequestClient class.
The open-source LINEJS library (available at evex-dev/linejs) implements a complete Thrift-based communication layer to interact with LINE's undocumented HTTP API. Unlike typical REST clients that exchange JSON, LINEJS constructs binary Thrift structs, sends them with application/x-thrift content types, and parses the binary responses back into JavaScript objects. This article examines the exact mechanism—from argument serialization to error handling—based on the library's TypeScript implementation.
Building the Thrift Request
The process begins when the client prepares a method call. LINEJS does not use IDL-generated classes; instead, it builds requests dynamically using a NestedArray structure that mirrors Thrift's field encoding.
The NestedArray Structure
Each argument becomes a tuple containing the Thrift type, field ID, and value:
// [ fieldType, fieldId, value ]
const args: NestedArray = [
[Thrift.Type.I64, 1, 123456789n], // long userId
[Thrift.Type.STRING, 2, "Hello world"], // message text
];
This array format allows the client to describe arbitrary Thrift structs without compile-time code generation.
Serializing with writeThrift
Inside RequestClient.requestCore, the library calls writeThrift to convert the NestedArray into a binary payload:
const Trequest = this.client.thrift.writeThrift(value, methodName, protocol);
The implementation in packages/linejs/base/thrift/readwrite/write.ts (lines 14-45) performs three critical steps:
- Opens a
TBufferedTransportstream - Writes a protocol-specific header via
genHeader - Recursively walks the NestedArray, emitting binary field markers through
writeValueand_writeStruct
The resulting Uint8Array contains the complete Thrift message ready for transmission.
Transmitting the Binary Payload
Once serialized, the binary payload travels via standard HTTP POST requests. The RequestClient manages all network communication in packages/linejs/base/request/mod.ts.
HTTP Headers and Endpoints
The client assembles mandatory headers including authentication tokens and protocol identifiers:
const response = await this.client.fetch(
`https://${this.endpoint}${path}`,
{
method: overrideMethod,
headers: {
"content-type": "application/x-thrift",
"accept": "application/x-thrift",
"x-line-access": authToken, // if authenticated
},
signal: AbortSignal.timeout(timeout),
body: Trequest,
},
);
All requests target LINE's legy.line-apps.com domain (or regional equivalents) with paths like /S3 for Talk services. The getHeader method (lines 72-84 in request/mod.ts) handles the precise header composition required by LINE's servers.
Parsing the Thrift Response
After receiving the HTTP response, LINEJS converts the ArrayBuffer back into structured data through the Thrift reader pipeline.
Deserializing with readThrift
The requestCore method passes the raw bytes to readThrift:
const parsedBody = new Uint8Array(body);
const res = this.client.thrift.readThrift(parsedBody, protocol);
Located in packages/linejs/base/thrift/readwrite/read.ts (lines 22-30), this function:
- Wraps the binary data in a
TFramedTransport - Uses
TCompactProtocolorTBinaryProtocol(specified by theprotocolparameter) to read the message header - Recursively reconstructs JavaScript objects via
readStructandreadValue, handling nested structs, lists, maps, and sets
Field Mapping and Error Handling
Raw Thrift responses contain numeric field IDs rather than human-readable names. LINEJS post-processes these through a renaming layer and implements LINE-specific error recovery.
Renaming Thrift Field IDs
The rename_data function in packages/linejs/base/thrift/rename/parser.ts (lines 71-133) maps numeric field IDs back to their original names:
if (parse === true) {
this.client.thrift.rename_data(res, square.includes(path));
}
This step also normalizes LINE-specific error structures like TalkException and SquareException into friendly JavaScript error objects, making the API responses intelligible to developers.
Token Refresh and Exception Handling
The RequestClient inspects every response for error structs (specifically res.data.e). When the error code equals MUST_REFRESH_V3_TOKEN, the client automatically refreshes the authentication token and retries the request recursively with isReRequest = true. For fatal errors like NOT_AUTHORIZED_DEVICE, it clears stored credentials and emits an "end" event. This logic resides in the error-handling block of requestCore (lines 30-50 in request/mod.ts).
Practical Implementation Examples
Sending a Talk Message
This example demonstrates the high-level API that encapsulates the entire Thrift flow:
import { LineClient } from "@evex/linejs";
const client = new LineClient({
// device details and auth token configuration
});
await client.login(); // ensures auth token is set
// Build Thrift arguments for TalkService.sendMessage
const args = [
[client.thrift.Type.I64, 1, 123456789n], // target userId
[client.thrift.Type.STRING, 2, "Hello from LINEJS!"], // text
];
// Use the high-level request helper
const result = await client.request.request(
args,
"sendMessage",
3, // ProtocolKey (3 = TBinaryProtocol)
true, // parse response
"/S3", // Talk endpoint
);
console.log("Message sent, server response:", result);
Manual Thrift Read/Write
For advanced use cases, you can bypass RequestClient and use the thrift utilities directly:
import { Thrift, Protocols } from "@evex/linejs/base/thrift/mod.ts";
import { writeThrift, readThrift } from "@evex/linejs/base/thrift/readwrite/mod.ts";
const binary = writeThrift(
[
[Thrift.Type.STRING, 1, "ping"],
],
"pingMethod",
Protocols[3], // Binary protocol
);
const response = await fetch("https://legy.line-apps.com/S3", {
method: "POST",
headers: {
"content-type": "application/x-thrift",
"accept": "application/x-thrift",
"x-line-application": "...your system type...",
"user-agent": "Line/10.13.0",
},
body: binary,
});
const raw = new Uint8Array(await response.arrayBuffer());
const parsed = readThrift(raw, Protocols[3]);
console.log("Parsed response:", parsed);
Summary
- Binary Serialization: LINEJS uses
writeThriftinpackages/linejs/base/thrift/readwrite/write.tsto convert NestedArray arguments into Thrift binary format. - HTTP Transport: The
RequestClientclass transmits payloads withContent-Type: application/x-thriftto LINE'slegy.line-apps.comendpoints. - Response Parsing:
readThriftinpackages/linejs/base/thrift/readwrite/read.tsdeserializes binary responses back into JavaScript objects. - Field Mapping: The
rename_datafunction translates numeric Thrift field IDs to human-readable names and extracts error structs. - Automatic Recovery: The client handles
MUST_REFRESH_V3_TOKENerrors by refreshing tokens and retrying requests automatically.
Frequently Asked Questions
What protocol does LINEJS use to communicate with LINE servers?
LINEJS uses Apache Thrift over HTTP. According to the evex-dev/linejs source code, it serializes requests into binary Thrift payloads using either TBinaryProtocol (ProtocolKey 3) or TCompactProtocol, transmitted with the application/x-thrift content type.
Where is the Thrift serialization logic implemented in LINEJS?
The Thrift serialization logic resides in packages/linejs/base/thrift/readwrite/write.ts for encoding and packages/linejs/base/thrift/readwrite/read.ts for decoding. The writeThrift function (lines 14-45) handles binary output, while readThrift (lines 22-30) handles parsing responses.
How does LINEJS handle authentication token expiration?
When the Thrift response contains a TalkException with code MUST_REFRESH_V3_TOKEN, the RequestClient in packages/linejs/base/request/mod.ts automatically refreshes the access token and re-issues the request. For NOT_AUTHORIZED_DEVICE errors, it clears the token and emits an "end" event.
Can I use LINEJS Thrift utilities without the full client?
Yes. The library exposes low-level Thrift functions in packages/linejs/base/thrift/readwrite/mod.ts. You can import writeThrift and readThrift directly to manually serialize NestedArray structures and parse binary responses without instantiating the full LineClient.
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 →