Appearance
Bring an account from another system
An account that already exists in your own system, with a session and the phone it ran on, comes onto its own virtual phone without a checkpoint of ours: you build a snapshot from its session (and its phone, when you hold it) and launch it. Every member is typed from the Simulator's own state (AndroidSnapshotInput).
What you give
- The phone is given whole or not at all. A complete phone state is kept exactly as given. Without one, the whole phone is made from
seed(a random one without a seed) the wayinstallmakes it; nothing is ever filled into a phone you gave, so aseedbeside a given phone is a type error. The app profile is a complete profile or none, and none is the Simulator's own build. - The
sessionis any subset of the session state, andcookiesis the serialized cookie jar. Both are optional. A signed-in session needsauthorization, theBearer IGT:2:…value the app sent with the account's requests. The account's id is read from it whendsUserIdis omitted. The other session fields are optional; their types are inAndroidSnapshotInput. - The user agent is not an input: the Simulator builds it from the phone and the app profile.
ts
import { AndroidIgpapi, type AndroidSnapshotInput, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile } from 'node:fs/promises';
// What your own system holds about the account. `authorization` is the `Bearer IGT:2:…` value the app sent
// with the account's requests; it is the one field a signed-in session needs.
const accountId = 'account-42';
const authorization: `Bearer ${string}` = `Bearer ${process.env['ACCOUNT_AUTHORIZATION'] ?? ''}`;
// With a session and no phone: the signed-in app's cold start on a new phone made from the seed.
// The program gets the feed; a challenge the start runs into is `otherwise`'s (or rejects the call).
const feedLength = await AndroidIgpapi.launch({
seed: accountId,
snapshot: { session: { authorization } },
main: screen => Effect.succeed(FeedTimelineScreen.is(screen) ? screen.items.length : 0),
});
console.log(feedLength);
// With the account's own phone as well: the whole device state, read from your storage, is kept exactly as given.
const phone: AndroidSnapshotInput['device'] = JSON.parse(await readFile('phone.json', 'utf8'));
const onItsPhone = await AndroidIgpapi.launch({
snapshot: { device: phone, session: { authorization } },
main: screen => Effect.succeed(FeedTimelineScreen.is(screen) ? screen.items.length : 0),
});
console.log(onItsPhone);
// Without a session: the login screen on a new phone made from the seed, to log in with the password.
await AndroidIgpapi.launch({
seed: accountId,
snapshot: {},
main: home =>
Effect.gen(function* () {
const next = yield* home.logIn({
username: process.env['IG_USERNAME'] ?? '',
password: process.env['IG_PASSWORD'] ?? '',
});
return next.id;
}),
});What you get
Whether the session is passed decides the type of the first screen, so the program needs no narrowing in that case. A session the server rejects fails the launch exactly as a stored session does: the call rejects with the typed IgLoginRequiredError. A phone or an app profile that is given but not complete rejects with SnapshotDecodeError, and so does a signed-in session that names no account (no dsUserId, and an authorization that is not the app's own Bearer IGT:2: one). A session without a phone and without a seed is allowed (the phone is then a random new one), but the account then appears on a phone it has not used before, and Instagram may answer with a challenge: pass the account's original phone to avoid it. After the launch checkpoint() gives the Simulator's own format (Keep a session).