Appearance
Registration
What the person sees. Creating an account, one screen per question: the phone number (RegistrationPhoneNumberScreen), the confirmation code (RegistrationConfirmationCodeScreen), the password (RegistrationPasswordScreen), the birthday (RegistrationBirthdayScreen), the name (RegistrationNameScreen), the username (RegistrationUsernameScreen) and the terms and policies (RegistrationTermsOfServiceScreen). The login screen's "Create new account" opens the first one.
What you do. Each screen's action submits what the person typed and opens the next, and the terms screen's agree() creates the account and answers the server's decision.
The "Join Instagram" screen at the start of sign-up, behind "Create new account".
The flow's state lives in the process; you pass only what the person types. A step refuses only when the server-issued data it needs is missing (RegistrationStateMissingError); a repeated call sends again, because you wrote it twice.
Every example starts a fresh install: AndroidIgpapi.install({ seed, main }) hands main the login screen. The examples answer no errors, so that each one stays on its path. Every action can fail with typed errors; answer them with Effect.catchTag, as Create an account does (Errors), and challenges with your otherwise constant (Challenges).
ts
import { AndroidIgpapi } from '@igpapi/android';
import { Effect } from 'effect';
import { createInterface } from 'node:readline/promises';
// Placeholders: the person's answers come from your own system.
const phoneNumber = process.env['IG_PHONE_NUMBER'] ?? '';
const password = process.env['IG_PASSWORD'] ?? '';
const fullName = 'Jessica Miller';
const username = 'jessica.miller.97';
// The SMS code is read when the step has sent it ("Codes that arrive during the run"): here a person types it
// at the terminal; your program can read an SMS provider instead.
const readCode = Effect.promise(async () => {
const terminal = createInterface({ input: process.stdin, output: process.stdout });
const code = await terminal.question('The SMS code: ');
terminal.close();
return code.trim();
});The matrix
| # | The person... | Starts on | Actions | Result |
|---|---|---|---|---|
| 1 | Registers an account | the login screen | createNewAccount(), submitPhoneNumber(), submitConfirmationCode(), submitPassword(), submitBirthday(), submitName(), submitUsername(), agree() | The server's decision: Created, ChallengeRequired or PartiallyCreated |
| 2 | Reads the decision | the terms screen | agree(), then _tag | The first screen of the launch, the account to challenge, or the partial account |
| 3 | Runs the launch again after it failed | the terms screen | retryLaunch() | The first screen of the launch; the account is not created twice |
The SMS code exists only after the number is submitted: the examples read it between submitPhoneNumber and submitConfirmationCode, as a person reads it from the phone (Codes that arrive during the run).
The scenarios
1. Register an account
ts
// Register an account: one screen per question, each action submits the answer and opens the next screen.
const outcome = await AndroidIgpapi.install({
seed: 'new-account',
main: home =>
Effect.gen(function* () {
const phone = yield* home.createNewAccount();
const confirmation = yield* phone.submitPhoneNumber(phoneNumber);
// The SMS exists only after the number is submitted: read the code now.
const smsCode = yield* readCode;
const passwordScreen = yield* confirmation.submitConfirmationCode(smsCode);
const birthday = yield* passwordScreen.submitPassword(password);
const name = yield* birthday.submitBirthday({ day: 14, month: 6, year: 1997 });
const usernameScreen = yield* name.submitName(fullName);
const terms = yield* usernameScreen.submitUsername(username);
// "I agree" creates the account and answers the server's decision.
return yield* terms.agree();
}),
});
console.log(outcome._tag);2. Read the server's decision
Only Created continues the process, and only its variant carries the first screen. ChallengeRequired is the account that exists but is not active until the person passes Instagram's verification challenge: no launch ran and the process holds no session. Log in to the new account with install as usual; if Instagram still asks for the verification, your otherwise branches answer it (Create an account).
ts
// Read the server's decision: created, a challenge to pass, or a partial creation.
const decision = await AndroidIgpapi.install({
seed: 'new-account',
main: home =>
Effect.gen(function* () {
const phone = yield* home.createNewAccount();
const confirmation = yield* phone.submitPhoneNumber(phoneNumber);
const smsCode = yield* readCode;
const passwordScreen = yield* confirmation.submitConfirmationCode(smsCode);
const birthday = yield* passwordScreen.submitPassword(password);
const name = yield* birthday.submitBirthday({ day: 14, month: 6, year: 1997 });
const usernameScreen = yield* name.submitName(fullName);
const terms = yield* usernameScreen.submitUsername(username);
const result = yield* terms.agree();
switch (result._tag) {
// The account is active: the action continued the process and carries the first screen.
case 'Created':
return result.firstScreen.id;
// The account exists, but it is not active until the person passes the verification challenge.
case 'ChallengeRequired':
return result.account.pkId;
// The server created part of it and issued nothing to complete it.
case 'PartiallyCreated':
return result.username;
}
}),
});
console.log(decision);3. Run the launch again
A launch that fails after the account exists is the typed RegisteredButLaunchFailedError, and the terms screen stays on top. retryLaunch() runs only the launch, also after a restored checkpoint.
ts
// The account was created but the launch after it failed: run only the launch again.
const launched = await AndroidIgpapi.install({
seed: 'new-account',
main: home =>
Effect.gen(function* () {
const phone = yield* home.createNewAccount();
const confirmation = yield* phone.submitPhoneNumber(phoneNumber);
const smsCode = yield* readCode;
const passwordScreen = yield* confirmation.submitConfirmationCode(smsCode);
const birthday = yield* passwordScreen.submitPassword(password);
const name = yield* birthday.submitBirthday({ day: 14, month: 6, year: 1997 });
const usernameScreen = yield* name.submitName(fullName);
const terms = yield* usernameScreen.submitUsername(username);
// The terms screen stays on top after a failed launch, and `retryLaunch` never registers twice.
const first = yield* terms.agree().pipe(
Effect.map(result => (result._tag === 'Created' ? result.firstScreen : undefined)),
Effect.catchTag('RegisteredButLaunchFailedError', () => terms.retryLaunch()),
);
return first?.id;
}),
});
console.log(launched);