# How the LINEJS Client Listens to Messages and Events: A Deep Dive into the Polling Architecture

> Learn how the LINEJS Client listens to messages and events. Explore its polling architecture, push stream management, and typed event emission for seamless communication.

- Repository: [Evex  Developers/linejs](https://github.com/evex-dev/linejs)
- Tags: deep-dive
- Published: 2026-03-01

---

**The LINEJS Client listens to messages and events by creating a `Polling` instance that manages push streams for Talk (chat) and Square (OpenChat) operations, decrypts E2EE payloads, and emits strongly-typed events through a `TypedEventEmitter`.**

The `evex-dev/linejs` library provides a high-level `Client` class that abstracts the complexity of LINE's real-time messaging protocol. When you invoke the `listen()` method, the client initializes a sophisticated pipeline that handles push connections, end-to-end encryption (E2EE) decryption, and event emission. This article examines the internal mechanics of how the LINEJS Client listens to messages and events, tracing the flow from the initial method call through to the final typed event emission.

## Architecture Overview

The listening architecture in LINEJS is built on a layered design that separates transport concerns from high-level event handling. At the core lies the `BaseClient`, which manages the underlying push connection to LINE's servers. The `Client` class in [`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts) acts as a consumer-facing wrapper that orchestrates the `Polling` mechanism, E2EE decryption, and event emission.

The system utilizes two primary push streams provided by `BaseClient.push`:
- **`opStream`** – Handles Talk operations (personal and group chat messages)
- **`sqStream`** – Handles Square events (OpenChat notifications and activities)

These streams are managed by the `Polling` class in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts), which ensures stream renewal and connection persistence through the legacy LINE push service.

## The Listening Flow

When you call `client.listen()`, the Client executes a precise sequence of operations to establish the message listening pipeline.

### Initializing the Polling Instance

The process begins with the creation of a `Polling` instance via `this.base.createPolling()`. This method, defined in [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts), instantiates a new `Polling` object that holds synchronization state and manages the push connection lifecycle.

The `Polling` class is responsible for:
- Maintaining the push connection to LINE's servers
- Renewing streams when connections drop
- Managing the legacy pusher initialization via `initLegyPusher()`

### Starting Talk and Square Event Loops

Once the `Polling` instance is ready, `client.listen()` optionally starts two asynchronous loops based on the provided options:

1. **Talk Events Loop**: When `talk: true` is set, the client iterates over `polling.listenTalkEvents()`. This method (located in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) lines 80-84) renews the `opStream`, ensures the legacy pusher is active, and returns `opStream.stream` as a `ReadableStream<Operation>`.

2. **Square Events Loop**: When `square: true` is set, the client uses `polling.listenSquareEvents()` to consume `sqStream`, yielding `SquareEvent` objects for OpenChat notifications.

Both loops respect the `AbortSignal` provided in the listen options, allowing for graceful shutdown when the signal is aborted.

### Decrypting E2EE Messages

For Talk events, the client filters for message operations specifically. When the event type is `SEND_MESSAGE` or `RECEIVE_MESSAGE`, the raw operation requires decryption before emission.

The client calls `this.base.e2ee.decryptE2EEMessage(event.message)` from [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts). This method handles the end-to-end encryption protocol, ensuring that the resulting `TalkMessage` object contains plaintext fields such as `text`, `stickerId`, and other content metadata.

### Emitting Typed Events

After processing, the client emits events through the `TypedEventEmitter` pattern defined in [`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts). The `Client` class declares specific event types:

- **`"event"`**: Emits raw `LINETypes.Operation` objects for all Talk operations
- **`"message"`**: Emits fully-decrypted `TalkMessage` objects for chat messages
- **`"square:event"`**: Emits `LINETypes.SquareEvent` objects for OpenChat activities
- **`"square:message"`**: Emits `SquareMessage` objects for OpenChat notifications

This type-safe emission allows consumers to listen to specific event types with full TypeScript support and automatic decryption handling.

## Key Components Deep Dive

Understanding the internal components reveals how LINEJS maintains stable real-time connections.

### Polling Class ([`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts))

The `Polling` class manages the lifecycle of push connections. Its primary responsibilities include:

- **Stream Renewal**: Methods like `opStream.renew()` and `sqStream.renew()` recreate HTTP/2 or WebSocket connections when the underlying transport fails.
- **Legacy Pusher Initialization**: The `initLegyPusher()` method opens the legacy LINE push service, registering target channels (`listenTarget = [3, 8]`) to maintain connection persistence.
- **Deprecated Generators**: The class includes `*_listenTalkEvents` and `*_listenSquareEvents` generators that represent the original HTTP-polling fallback mechanism, though the current implementation prefers direct push streams.

### Push Connection Management

The `BaseClient.push` property provides the low-level transport layer:
- **`opStream`**: A `ReadableStream` yielding `Operation` objects for Talk events
- **`sqStream`**: A `ReadableStream` yielding `SquareEvent` objects for OpenChat

These streams abstract the underlying protocol complexity, whether using HTTP/2 streams or WebSocket connections, providing a consistent async iterator interface for the `Polling` class.

### TypedEventEmitter ([`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts))

This generic class provides type-safe event handling. The `Client` extends this emitter with a specific `ClientEvents` type map:

```typescript
export type ClientEvents = {
  message: (msg: TalkMessage) => void;
  event: (op: LINETypes.Operation) => void;
  "square:message": (msg: SquareMessage) => void;
  "square:event": (ev: LINETypes.SquareEvent) => void;
};

```

This architecture ensures that TypeScript users receive full IntelliSense and type checking when registering event listeners, eliminating runtime errors from incorrect event names or handler signatures.

## Practical Implementation Examples

Implementing message listening requires understanding the event lifecycle and proper resource management.

### Basic Message Listener Setup

To start listening for personal and group chat messages, instantiate the `Client` and register the `"message"` event listener:

```typescript
import { Client } from "@evex/linejs/client";

const client = new Client(base); // `base` is an authenticated BaseClient

client.on("message", (msg) => {
  console.log("🗨️ Talk message:", msg.text);
});

// Start listening with default options (talk: true, square: true)
client.listen({
  talk: true,
  square: true,
});

```

The `msg` object is a fully-decrypted `TalkMessage` instance defined in [`packages/linejs/client/features/message/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/features/message/mod.ts), containing plaintext fields like `text`, `stickerId`, and sender metadata.

### Handling Square (OpenChat) Events

For OpenChat notifications, listen to the `"square:message"` and `"square:event"` events:

```typescript
client.on("square:message", (msg) => {
  console.log("🔲 Square notification:", msg.text);
});

client.on("square:event", (ev) => {
  console.log("Raw square event type:", ev.type);
});

```

These events originate from the `sqStream` processed in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) and are filtered for `NOTIFICATION_MESSAGE` types before being wrapped in `SquareMessage` objects.

### AbortController for Graceful Shutdown

To properly stop listening and close connections, provide an `AbortSignal`:

```typescript
const controller = new AbortController();

client.listen({
  talk: true,
  square: false,
  signal: controller.signal,
});

// Gracefully stop after 5 minutes
setTimeout(() => controller.abort(), 5 * 60_000);

```

When aborted, the signal triggers cleanup in `client.listen()` that closes both `opStream` and `sqStream` connections, ensuring no resource leaks occur.

## Summary

- The **LINEJS Client** listens to messages through the `listen()` method in [`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts), which orchestrates the entire pipeline.
- **Polling management** is handled by the `Polling` class in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts), which renews push streams and maintains the legacy pusher connection.
- **Dual stream architecture** processes Talk events via `opStream` and Square (OpenChat) events via `sqStream`, both provided by `BaseClient.push`.
- **E2EE decryption** occurs automatically in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) before message events are emitted, ensuring plaintext content in `TalkMessage` objects.
- **Type-safe events** are emitted through `TypedEventEmitter` ([`packages/linejs/base/core/typed-event-emitter/index.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/typed-event-emitter/index.ts)) with strongly-typed handlers for `"message"`, `"square:message"`, and raw operation events.

## Frequently Asked Questions

### How does the LINEJS Client handle connection drops during listening?

The `Polling` class in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) automatically handles connection drops by calling `opStream.renew()` and `sqStream.renew()` methods. These methods recreate the underlying HTTP/2 or WebSocket streams when the connection fails. Additionally, `initLegyPusher()` maintains the legacy LINE push service registration with `listenTarget = [3, 8]` to ensure the connection remains alive and automatically reconnects when network issues occur.

### What is the difference between the "event" and "message" events in LINEJS?

The `"event"` emitter in [`packages/linejs/client/client.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/client.ts) emits raw `LINETypes.Operation` objects for every Talk operation received through the `opStream`, providing unfiltered access to all protocol-level events. In contrast, the `"message"` event emits fully processed `TalkMessage` instances that have been filtered specifically for `SEND_MESSAGE` and `RECEIVE_MESSAGE` operation types, and have passed through E2EE decryption in [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts) to ensure plaintext content is available.

### Can I listen to only Talk messages without Square (OpenChat) events?

Yes, the `listen()` method accepts an options object that allows selective enabling of event streams. By passing `{ talk: true, square: false }` to `client.listen()`, the Client will only initialize the Talk event loop via `polling.listenTalkEvents()` while skipping the Square event loop. This reduces resource usage and network traffic if you do not need OpenChat notifications, and the `opStream` will be the only active push connection maintained by the `Polling` instance.

### How does LINEJS decrypt end-to-end encrypted messages automatically?

Before emitting a `"message"` event, the Client checks if the operation type is `SEND_MESSAGE` or `RECEIVE_MESSAGE` and then calls `this.base.e2ee.decryptE2EEMessage(event.message)` from [`packages/linejs/base/e2ee/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/e2ee/mod.ts). This method handles the cryptographic decryption of the E2EE payload using the client's encryption keys, transforming the raw encrypted message into a plaintext format. The decrypted content is then wrapped in a `TalkMessage` object (defined in [`packages/linejs/client/features/message/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/features/message/mod.ts)) and emitted via the `"message"` event with accessible fields like `text` and `stickerId`.