# How to Implement Multi-Device Sync and Session Management in LINEJS

> Learn to implement multi-device sync and session management in LINEJS using authToken, sessionId, and syncToken. Synchronize real-time state across Talk and Square.

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

---

**LINEJS implements a single-session, multi-device architecture using `authToken` for HTTP credentials, `sessionId` for cryptographic device binding, and `syncToken` as an incremental cursor for real-time state synchronization across Talk and Square services.**

LINEJS is an open-source TypeScript library that replicates the official LINE client protocol, enabling developers to build applications that sync messages and events across multiple devices simultaneously. Understanding how to implement multi-device sync and session management in LINEJS requires familiarity with its three-tier identifier system and persistent push-based connection model.

## Understanding LINEJS Session Architecture

LINEJS maintains session state through three distinct identifiers that work together to enable secure, synchronized communication across devices.

### Core Session Identifiers

The library manages three primary identifiers, each serving a specific purpose in the authentication and synchronization flow:

- **`authToken`**: A short-lived JWT-style credential stored in `BaseClient.authToken` (defined in [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts)). The `Login.login`, `Login.withQrCode`, and `Login.withPassword` methods in [`packages/linejs/base/login/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/login/mod.ts) set this token after successful authentication. It automatically attaches to every HTTP request via the `RequestClient` interceptor.

- **`sessionId` / `sessionKey`**: A persistent cryptographic key generated during QR login via `Login.requestSQR` or `Login.requestSQR2`. This identifier binds a specific device instance to the LINE account and is required for E2EE operations and service calls like `E2EE.createSqrSecret`.

- **`syncToken`**: An incremental cursor stored in `BaseClient.poll.sync.<service>` that tracks the client's position in the event stream. The `ConnManager._OnSignOnResponse` method in [`packages/linejs/base/push/connManager.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/push/connManager.ts) updates these tokens after every fetch operation, while [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) maintains the persistent state.

## Initializing Sessions and Multi-Device Login

Creating a multi-device setup involves instantiating separate `Client` instances, each representing a distinct device within the same LINE account.

### QR Code and Password Authentication

The primary entry points for session creation are `loginWithQR` and `loginWithPassword`, both of which orchestrate the handshake with LINE's servers:

```typescript
import { loginWithQR } from "@evex/linejs/client/login.ts";

const client = await loginWithQR(
  {
    onReceiveQRUrl: async (url) => console.log("Scan QR:", url),
    onPincodeRequest: (pin) => console.log("Enter PIN:", pin),
  },
  {
    device: "iOS",
    version: "14.0",
    endpoint: "legy.line-apps.com",
  },
);

```

Under the hood, `loginWithQR` calls `Login.requestSQR` (QR v1) or `Login.requestSQR2` (QR v2), which return a `sessionId` and a certificate. After the user scans the QR code and verifies the PIN, the server returns the `authToken` that gets stored in `BaseClient.authToken`. The `Login.registerCert` method then persists the device certificate for future logins.

### Device-Specific Session Binding

Each client instance represents a physical device through the `Device` type defined in [`packages/linejs/base/utils/devices.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/utils/devices.ts). The library validates device strings (e.g., "iOS", "Android") against supported versions using `getDeviceDetails`.

```typescript
const clientA = await loginWithQR(..., { device: "iOS", version: "13.5" });
const clientB = await loginWithQR(..., { device: "Android", version: "11" });

```

Both clients maintain independent `authToken` values but share the underlying LINE account. Because the `sessionId` is cryptographically bound to the device parameters, each client receives only its own device-specific push events through separate `ConnManager` instances.

## Implementing Real-Time Multi-Device Sync

LINEJS opens a persistent HTTP/2 push connection for each client to receive real-time updates, ensuring all devices stay synchronized without polling.

### Push Connection Architecture

The `ConnManager` class in [`packages/linejs/base/push/connManager.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/push/connManager.ts) manages the H2 connection lifecycle. To start synchronization, invoke `InitAndRead` with the service identifiers you want to sync:

```typescript
await client.push.InitAndRead([3, 5]); // 3 = Square, 5 = Talk

```

The `InitAndRead` method sends a **sign-on request** containing the current `syncToken` for each service. The server responds with a payload that includes new events and an updated token. The `_OnSignOnResponse` handler extracts these values and updates the client's state:

```typescript
// Inside ConnManager._OnSignOnResponse
this.client.poll.sync.square = newSquareToken;
this.client.poll.sync.talk.revision = response.fullSyncResponse.nextRevision;

```

### Sync Token Lifecycle

The sync token mechanism ensures **exactly-once delivery** and ordering guarantees. When the client receives a response, it immediately uses the new token for the next sign-on request, creating a continuous chain. The `Polling` module in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) exposes these tokens via `client.poll.sync`, allowing applications to track synchronization state across Talk and Square services.

## Session Persistence and Restoration

LINEJS supports session restoration to eliminate repeated QR scans or password entry when restarting applications or switching between devices.

### Token-Based Re-authentication

Store the `authToken` securely after the initial login, then restore the session by injecting it into a new `Client` instance:

```typescript
import { Client } from "@evex/linejs/base/core/mod.ts";

const savedToken = await secureStorage.get("authToken");

const client = new Client({
  device: "Android",
  version: "12",
  storage: new MemoryStorage(),
});

// Restore session without QR/password
await client.loginProcess.login({ authToken: savedToken });
await client.push.InitAndRead([3, 5]);

```

The `loginProcess.login` method in [`packages/linejs/base/login/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/login/mod.ts) accepts the `authToken` parameter and bypasses the interactive authentication flow, immediately setting `BaseClient.authToken` and initializing the service layer.

### Handling Auth Token Refresh

When the `authToken` expires or the user changes their password, `ConnManager._OnPingCallback` detects the token mismatch and automatically re-establishes the push connection:

```typescript
if (oldToken !== newToken && newToken) {
  this.authToken = newToken;
  this.conns[0].close(); // Triggers reconnection with new credentials
}

```

This mechanism ensures that **all active devices** maintain valid connections even when the primary authentication credentials rotate.

## Advanced Sync Token Management

While LINEJS automatically manages sync tokens during push operations, applications requiring custom pagination or historical event retrieval can manipulate these tokens directly.

### Manual Pagination Control

To fetch older Square events manually, retrieve the current token from `client.poll.sync.square` and pass it to `fetchMyEvents`:

```typescript
const current = client.poll.sync.square;

const { events, syncToken } = await client.square.fetchMyEvents({
  subscriptionId: 12345,
  syncToken: current,
  limit: 200,
});

client.poll.sync.square = syncToken; // Update state for continuity

```

For Talk synchronization, use the `talk.sync` method with the revision numbers stored in `client.poll.sync.talk`:

```typescript
const sync = client.poll.sync.talk;
const res = await client.talk.sync({
  request: {
    lastRevision: sync.revision - 500, // Historical pagination
    count: 200,
    lastGlobalRevision: sync.globalRev,
    lastIndividualRevision: sync.individualRev,
  },
});

```

The `client.square.fetchMyEvents` implementation in [`packages/linejs/client/features/square/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/client/features/square/mod.ts) and the `talk.sync` implementation in [`packages/linejs/base/service/talk/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/service/talk/mod.ts) both rely on the `Polling` state managed in [`packages/linejs/base/polling/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/polling/mod.ts) to maintain consistency with the automatic push synchronization.

## Summary

- **LINEJS uses three identifiers** for session management: `authToken` (HTTP credentials), `sessionId` (device binding), and `syncToken` (event cursor).
- **Multi-device support** is achieved by instantiating separate `Client` objects with unique device parameters, each maintaining independent push connections via `ConnManager`.
- **Real-time synchronization** relies on persistent HTTP/2 connections where `InitAndRead` sends sign-on requests containing the current `syncToken`, and `_OnSignOnResponse` updates the token for the next request.
- **Session restoration** is possible by storing the `authToken` and calling `loginProcess.login({ authToken: savedToken })`, bypassing interactive authentication.
- **Automatic recovery** handles token refreshes by closing and reopening push connections when `authToken` changes, ensuring continuous synchronization across all devices.

## Frequently Asked Questions

### How does LINEJS handle authentication across multiple devices simultaneously?

LINEJS creates a unique `sessionId` for each device during the QR or password login flow in [`packages/linejs/base/login/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/login/mod.ts). While all devices share the same LINE account, each maintains an independent `authToken` and push connection through `ConnManager`. The server routes events to each device based on its specific `sessionId`, allowing multiple clients to operate concurrently without interfering with each other's state.

### What is the difference between `authToken` and `syncToken` in LINEJS?

The `authToken` is a short-lived authentication credential stored in `BaseClient.authToken` that validates HTTP requests to LINE's servers. In contrast, the `syncToken` (stored in `client.poll.sync.talk` or `client.poll.sync.square`) is an incremental cursor that tracks the client's position in the event stream. While the `authToken` identifies *who* is making the request, the `syncToken` tells the server *where* the client left off in the message history.

### Can I restore a LINEJS session without scanning a QR code again?

Yes. Store the `authToken` after the initial successful login, then create a new `Client` instance and call `client.loginProcess.login({ authToken: savedToken })` as implemented in [`packages/linejs/base/login/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/login/mod.ts). This method bypasses the QR or password flow and immediately restores the session, though you must ensure the `authToken` has not expired if you intend to use it for an extended period.

### How does LINEJS ensure messages stay synchronized when the connection drops?

The `ConnManager` in [`packages/linejs/base/push/connManager.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/push/connManager.ts) monitors connection health through `_OnPingCallback`. If the connection drops or the `authToken` changes, the manager automatically closes the stale connection and reinitializes `InitAndRead` using the last known `syncToken` from `client.poll.sync`. This ensures that when the client reconnects, it requests only the events missed during the downtime, maintaining exactly-once delivery semantics.