Skip to content

Keep a session ​

A session is kept by checkpointing a screen. You take a checkpoint after the login, store it as a secret, and start from it in a later program, so the account is not logged in on every run.

Save it ​

A screen handle's checkpoint() returns the process at that screen as JSON: the whole session (the phone, the account's authorization and cookies) and the screens you have open. Saving and loading are plain JavaScript around the call: the program returns the checkpoint, and your code writes it where you keep state.

Every entry that takes a stored value accepts an object, never JSON text. Parse the file (JSON.parse) and hand the object over. A string, JSON or not, is refused with the entry's typed decode error.

The program below logs in, saves the checkpoint right after the login, and then starts from the stored file.

ts
import { AndroidIgpapi, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile, writeFile } from 'node:fs/promises';

const username = process.env['IG_USERNAME'] ?? '';
const password = process.env['IG_PASSWORD'] ?? '';

// One call is one app process, birth to death. A fresh install's first screen is the login screen;
// its login continues the same process through the app's launch.
const checkpoint = await AndroidIgpapi.install({
  seed: username,
  main: home =>
    Effect.gen(function* () {
      const next = yield* home.logIn({ username, password });
      // The checkpoint carries the whole session and the screens you have open, and its type
      // carries the screen it was taken on.
      return FeedTimelineScreen.is(next) ? yield* next.checkpoint() : undefined;
    }),
});
if (checkpoint === undefined) throw new Error('The login did not reach the feed: see "Log in".');
await writeFile('session.json', JSON.stringify(checkpoint));

// An entry takes the checkpoint as an object: parse what the storage returns.
const saved = JSON.parse(await readFile('session.json', 'utf8'));

// A stored process is a new app process: the launch starts the app for the stored user and `main` gets
// the screen the app shows first. Name the screen you expect and `main` gets exactly that handle.
const keys = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const item = yield* feed.swipeToNext();
      return [item?.key];
    }),
});
console.log(keys);

Start from it ​

  • AndroidIgpapi.launch({ checkpoint, … }) is a cold start that takes the checkpoint itself, the way a person opens the app again. main gets the screen the app shows first; name the screen you expect with screen: and it gets exactly that handle.
  • To continue on the checkpoint's own screen instead, use a resume (Ways to start the app).

Store it as a secret ​

A checkpoint contains credentials: the session (its authorization and cookies), the phone's private identifiers and the keys of the client it was taken from. Treat it like a password: store it as a secret, never log it, and hand it only to the process that continues it.

Which screen a launch opens ​

  • The checkpoint's screen picks the launch's screen. A checkpoint taken on a screen before sign-in (the login screen and the screens it opens: account recovery and two-factor) is a process without a session, and a launch from it opens the login screen. A checkpoint taken on any other screen belongs to a signed-in process, whose launch starts on the feed.
  • A checkpoint whose screen and session disagree (a signed-in screen without a session, or a screen before sign-in with one) is refused before anything is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent.
  • A checkpoint that crossed JSON storage no longer names its screen in its type, so its screen is the honest union: narrow it with FeedTimelineScreen.is, or name the screen you expect in the screen field.

Route a stored checkpoint ​

Screen.ownsCheckpoint(checkpoint) tells which screen a stored checkpoint belongs to before you launch it. For an object it narrows the type; for unknown it answers a plain boolean, and text is no checkpoint (false).

ts
import { LoginHomepageScreen } from '@igpapi/android';
import { readFile } from 'node:fs/promises';

// A checkpoint read back from your storage: its type no longer names its screen.
const stored: unknown = JSON.parse(await readFile('session.json', 'utf8'));

if (LoginHomepageScreen.ownsCheckpoint(stored)) {
  // The checkpoint was taken on the login screen: route it to the login flow.
  console.log('log in again');
}

When it cannot be read ​

A stored value that cannot be restored (a damaged cookie jar, a string instead of an object, or a phone, a session or an app profile that is not the shape the Simulator wrote) rejects the call with the typed SnapshotDecodeError. A damaged list of open screens, or a checkpoint of an earlier release (below), is CheckpointDecodeError. Both are expected failures, not programming errors: handle them, usually by logging in again, as below. Invalid options (a request timeout out of range, a protocol mode the platform does not have) are programming errors and reject as a defect.

ts
import { AndroidIgpapi, FeedTimelineScreen, SnapshotDecodeError } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile } from 'node:fs/promises';

const username = process.env['IG_USERNAME'] ?? '';
const password = process.env['IG_PASSWORD'] ?? '';

// The checkpoint read back from your storage.
const saved = JSON.parse(await readFile('session.json', 'utf8'));

// The errors of the start reject the call with the same tagged error.
const keys = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      const item = yield* feed.swipeToNext();
      return [item?.key];
    }),
}).catch(async (error: unknown) => {
  if (!(error instanceof SnapshotDecodeError)) throw error;
  // The saved session is unusable: start a new device and log in again.
  return AndroidIgpapi.install({
    seed: username,
    main: home =>
      Effect.gen(function* () {
        const firstScreen = yield* home.logIn({ username, password });
        return FeedTimelineScreen.is(firstScreen) ? [firstScreen.id] : [];
      }),
  });
});
console.log(keys);

Across an upgrade ​

A checkpoint from release 4.11.0 or later starts in any later release: launch it. A checkpoint from an earlier release is refused with CheckpointDecodeError; log in again. Resume a checkpoint only with the release that wrote it; after an upgrade, use launch. Upgrading lists what each release changes.

Snapshots ​

A snapshot exported with ExportSnapshot is the same stored form without the screens: AndroidIgpapi.launch({ snapshot, … }) accepts it, and it is the way to carry a session out of a program over the app layer (Compose the app layer yourself). A snapshot you build by hand brings an account from another system (Bring an account from another system). The exported snapshot is a copy, detached from the running process, and no entry changes the object you pass.

The pages of this site are the README files of the repository. The API reference is generated from the source.