IGPAPI
    Preparing search index...

    Function AndroidAppLaunchSimulation

    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Parameters

      • input: AndroidAppLaunchStoredOptions

      Returns Effect<AndroidAppLaunchFirstScreen, AndroidAppLaunchSimulationError, Scope>

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));
    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Type Parameters

      • Top extends
            | "LoginHomepage"
            | "LoginHomeCds"
            | "LandingScreen"
            | "LoginTwoFactorEntrypoint"
            | "LoginTwoFactorMethodPicker"
            | "LoginTwoFactorEnterTotpCode"
            | "AccountRecoverySearchCds"
            | "AccountRecoverySearch"
            | "AccountRecoveryAuthenticationConfirmation"
            | "AccountRecoveryEmailCode"
            | "RegistrationPhoneNumber"
            | "RegistrationConfirmationCode"
            | "RegistrationPassword"
            | "RegistrationBirthday"
            | "RegistrationName"
            | "RegistrationUsername"
            | "RegistrationTermsOfService"
            | "SaveCredentials"
            | "AccountRecoverySuccessLocationPermission"
            | "FeedTimeline"
            | "PostOpenedFromLink"
            | "ReelsViewer"
            | "MediaComments"
            | "RepostNoteEditor"
            | "Explore"
            | "SearchInput"
            | "SearchResultsTopTab"
            | "SearchResultsAccountsTab"
            | "UserProfile"
            | "ProfileMediaTab"
            | "MyProfile"
            | "MyProfileMediaTab"
            | "ProfilePostsFeed"
            | "DirectInbox"
            | "DirectRecipientPicker"
            | "DirectThread"
            | "DirectCustomizeChat"
            | "DirectThreadThemePicker"
            | "DirectThreadDetails"
            | "DirectThreadSharedMedia"
            | "DirectThreadNicknames"
            | "DirectThreadNicknameChange"
            | "DirectGroupNameAndPhotoEditor"
            | "ChallengeSelectVerifyMethod"
            | "ChallengeVerifyCode"
            | "ChallengeSubmitPhone"
            | "ChallengeSetNewPassword"
            | "ChallengeAddBirthday"
            | "ChallengeLoginReview"
            | "ChallengeAccountTemporarilyLocked"
            | "ChallengeEscalationInformation"
            | "ChallengeReviewContactPointChange"
            | "ChallengeScrapingWarning"
            | "ChallengeWebRecaptcha"
            | "ChallengeWebSetNewPassword"
            | "ChallengeTextCaptcha"
            | "ChallengeRecaptcha"
            | "ChallengeSetContactPoint"
            | "ChallengeContactPointCode"
            | "ChallengeAuthenticityWizard"
            | "ChallengeAwaitingReview"
            | "ChallengeThanksForProvidingInfo"
            | "ChallengeAccountSuspended"

      Parameters

      • input: AndroidAppLaunchTypedCheckpointOptions<Top>

      Returns Effect<CheckpointFirstScreen<Top>, AndroidAppLaunchSimulationError, Scope>

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));
    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Parameters

      • input: AndroidAppLaunchCheckpointOptions

      Returns Effect<AndroidAppLaunchFirstScreen, AndroidAppLaunchSimulationError, Scope>

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));
    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Parameters

      • Optionalinput: AndroidAppLaunchFreshOptions

      Returns Effect<
          ScreenRootHandle<
              "LoginHomepage",
              "screen",
              {
                  aac: string;
                  attestationChallengeNonce?: string;
                  loginEntryPoint: "login-home"
                  | "landing-screen";
                  oauthQpl: { instanceId: number; markerId: number };
                  passwordTextInputId: string;
                  qpl: { instanceId: number; markerId: number };
                  recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                  usernameTextInputId: string;
                  waterfallId: string;
              },
              {
                  createNewAccount: (
                      self: ScreenHandle,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => ScreenEdgeBound<
                      ScreenEdgeShape<RegistrationPhoneNumberDefinition, []>,
                      (
                          ...args: [],
                      ) => Effect<
                          AssembledHandle<
                              "RegistrationPhoneNumber",
                              "screen",
                              {},
                              {
                                  submitPhoneNumber: (
                                      self: ScreenHandle,
                                      state: State,
                                  ) => (...args: ...) => ...;
                              },
                              Record<never, never>,
                              ScreenHandle,
                              "required",
                          >,

                              | IgNotFoundError
                              | IgRateLimitError
                              | IgSpamError
                              | IgSignupBlockError
                              | IgCheckpointError
                              | IgAccountSuspendedError
                              | IgAccountDisabledError
                              | IgCookieConsentRequiredError
                              | IgLoginRequiredError
                              | IgUserLoggedOutError
                              | IgChallengeWrongCodeError
                              | IgPrivateUserError
                              | IgInactiveUserError
                              | IgSentryBlockError
                              | IgUploadVideoError
                              | IgConfigureVideoError
                              | IgExactUserNotFoundError
                              | IgUserIdNotFoundError
                              | IgNetworkError
                              | IgDecompressionError
                              | IgParseError
                              | IgIpBlockError
                              | IgLoginTwoFactorRequiredError
                              | IgRequestsLimitError
                              | IgNotResolvableError
                              | RequestError
                              | ParseError
                              | TransportError
                              | IgBloks429Error
                              | InvalidSkipOrdinalsError
                              | ScreenDataError
                              | ScreenEnterNavigationError
                              | ScreenEnterConcurrentError
                              | ScreenNotOnTopError
                              | ScreenEdgeInputError,
                      >,
                  >;
                  logIn: (
                      self: LoginHomepageFrame,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => (
                      ...args: [credentials: LoginInput],
                  ) => Effect<
                      LoginHomepageAnswer,
                      | IgNotFoundError
                      | IgRateLimitError
                      | IgSpamError
                      | IgSignupBlockError
                      | IgCheckpointError
                      | IgAccountSuspendedError
                      | IgAccountDisabledError
                      | IgCookieConsentRequiredError
                      | IgLoginRequiredError
                      | IgUserLoggedOutError
                      | IgChallengeWrongCodeError
                      | IgPrivateUserError
                      | IgInactiveUserError
                      | IgSentryBlockError
                      | IgUploadVideoError
                      | IgConfigureVideoError
                      | IgExactUserNotFoundError
                      | IgUserIdNotFoundError
                      | IgNetworkError
                      | IgDecompressionError
                      | IgParseError
                      | IgIpBlockError
                      | IgLoginTwoFactorRequiredError
                      | IgRequestsLimitError
                      | IgNotResolvableError
                      | RequestError
                      | ParseError
                      | TransportError
                      | PasswordEncryptionKeysUnavailableError
                      | LoginBloksParseError
                      | LoginBloksRejectedError
                      | LoginTwoFactorEntrypointParseError
                      | LoginAccountRecoveryRequiredError
                      | LoginAccountRecoveryContinueError
                      | IgBloks429Error
                      | IgLoginBadPasswordError
                      | InvalidSkipOrdinalsError
                      | AccountRecoveryBloksIssuedParamsMissingError
                      | ScreenDataError
                      | ScreenFeedPageError
                      | ScreenEnterNavigationError
                      | ScreenEnterConcurrentError
                      | ScreenLaunchFirstScreenError
                      | ScreenLaunchScreenNotInCatalogError
                      | ScreenLaunchSplashNotOnTopError
                      | ScreenLaunchSplashScreenError
                      | AppAlreadyStartedError
                      | ProcessNotSignedInError
                      | AccountRecoveryTicketUnknownError
                      | ScreenNotOnTopError
                      | ScreenEdgeInputError
                      | LoginPendingError
                      | TwoFactorTicketUnknownError,
                      | PlainJsonApiHttp
                      | MobileSession
                      | MobileDevice
                      | MobileApplication
                      | SessionHeaderParser
                      | BloksHttp
                      | MobileNavigation
                      | AccountRecoveryTickets
                      | LoginHomepageLaunch
                      | ScreenCatalog
                      | ScreenStack
                      | TwoFactorTickets,
                  >;
                  openApproveFromAnotherDevice: <Self extends ScreenHandle>(
                      self: Self,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => ScreenEdgeBound<
                      ScreenEdgeShape<
                          ScreenDefinition<
                              "LoginTwoFactorEntrypoint",
                              "screen",
                              undefined,
                              { ticket: string & Brand<"TwoFactorApprovalTicket"> },
                              LoginTwoFactorApproveFromAnotherDeviceState,

                                  | InvalidSkipOrdinalsError
                                  | ScreenEnterNavigationError
                                  | TwoFactorTicketUnknownError,
                              MobileNavigation
                              | TwoFactorTickets,
                              {
                                  checkApproval: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorApproveFromAnotherDeviceState,
                                  ) => (...args: []) => Effect<(...), (...), (...)>;
                                  tryAnotherWay: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorApproveFromAnotherDeviceState,
                                  ) => ScreenEdgeBound<
                                      ScreenEdgeShape<(...), (...)>,
                                      (...args: ...) => ...,
                                  >;
                              },
                              Record<never, never>,
                              never,
                          >,
                          [],
                      >,
                      (
                          ...args: [],
                      ) => Effect<
                          AssembledHandle<
                              "LoginTwoFactorEntrypoint",
                              "screen",
                              { ticket: string & Brand<(...)> },
                              {
                                  checkApproval: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorApproveFromAnotherDeviceState,
                                  ) => (...args: ...) => ...;
                                  tryAnotherWay: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorApproveFromAnotherDeviceState,
                                  ) => ScreenEdgeBound<(...), (...)>;
                              },
                              Record<never, never>,
                              Self,
                              "required",
                          >,

                              | InvalidSkipOrdinalsError
                              | ScreenDataError
                              | ScreenEnterNavigationError
                              | ScreenEnterConcurrentError
                              | ScreenNotOnTopError
                              | ScreenEdgeInputError
                              | TwoFactorTicketUnknownError,
                      >,
                  >;
                  openEmailCode: <Self extends ScreenHandle>(
                      self: Self,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => ScreenEdgeBound<
                      ScreenEdgeShape<
                          ScreenDefinition<
                              "AccountRecoveryEmailCode",
                              "screen",
                              undefined,
                              {
                                  maskedContact?: string;
                                  ticket: string & Brand<"LastResortCodeEntryTicket">;
                              },
                              {
                                  maskedContact?: string;
                                  ticket: string & Brand<"LastResortCodeEntryTicket">;
                              },
                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | AccountRecoveryBloksIssuedParamsMissingError
                              | ScreenEnterNavigationError
                              | AccountRecoveryTicketUnknownError,
                              | MobileSession
                              | MobileDevice
                              | MobileApplication
                              | BloksHttp
                              | MobileNavigation
                              | AccountRecoveryTickets,
                              {
                                  requestNewCode: (
                                      self: ScreenHandle,
                                      state: { maskedContact?: (...) | (...); ticket: (...) & (...) },
                                  ) => (...args: []) => Effect<(...), (...), (...)>;
                                  submitCode: (
                                      self: ScreenHandle,
                                      state: { maskedContact?: (...) | (...); ticket: (...) & (...) },
                                  ) => (...args: [code: ...]) => Effect<(...), (...), (...)>;
                              },
                              Record<never, never>,
                              never,
                          >,
                          [],
                      >,
                      (
                          ...args: [],
                      ) => Effect<
                          AssembledHandle<
                              "AccountRecoveryEmailCode",
                              "screen",
                              { maskedContact?: string; ticket: string & Brand<(...)> },
                              {
                                  requestNewCode: (
                                      self: ScreenHandle,
                                      state: { maskedContact?: ...; ticket: ... },
                                  ) => (...args: ...) => ...;
                                  submitCode: (
                                      self: ScreenHandle,
                                      state: { maskedContact?: ...; ticket: ... },
                                  ) => (...args: ...) => ...;
                              },
                              Record<never, never>,
                              Self,
                              "required",
                          >,

                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | AccountRecoveryBloksIssuedParamsMissingError
                              | ScreenDataError
                              | ScreenEnterNavigationError
                              | ScreenEnterConcurrentError
                              | AccountRecoveryTicketUnknownError
                              | ScreenNotOnTopError
                              | ScreenEdgeInputError,
                      >,
                  >;
                  openMethodPicker: <Self extends ScreenHandle>(
                      self: Self,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => ScreenEdgeBound<
                      ScreenEdgeShape<
                          ScreenDefinition<
                              "LoginTwoFactorMethodPicker",
                              "screen",
                              { ticket?: string & Brand<(...)> },
                              {
                                  offeredMethods: readonly (
                                      | "approve_from_another_device"
                                      | "whatsapp"
                                      | "totp"
                                      | "sms"
                                      | "backup_codes"
                                  )[];
                                  preselected?: | "approve_from_another_device"
                                  | "whatsapp"
                                  | "totp"
                                  | "sms"
                                  | "backup_codes";
                                  ticket: string & Brand<"TwoFactorPickerTicket">;
                              },
                              LoginTwoFactorChooseMethodState,
                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | ScreenEnterNavigationError
                              | TwoFactorTicketUnknownError,
                              | MobileDevice
                              | MobileApplication
                              | BloksHttp
                              | MobileNavigation
                              | TwoFactorTickets,
                              {
                                  chooseAuthenticationApp: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorChooseMethodState,
                                  ) => ScreenEdgeBound<
                                      ScreenEdgeShape<(...), (...)>,
                                      (...args: ...) => ...,
                                  >;
                              },
                              Record<never, never>,
                              never,
                          >,
                          [],
                      >,
                      (
                          ...args: [],
                      ) => Effect<
                          AssembledHandle<
                              "LoginTwoFactorMethodPicker",
                              "screen",
                              {
                                  offeredMethods: readonly ((...) | (...) | (...) | (...) | (...))[];
                                  preselected?:
                                      | "approve_from_another_device"
                                      | "whatsapp"
                                      | "totp"
                                      | "sms"
                                      | "backup_codes";
                                  ticket: string & Brand<(...)>;
                              },
                              {
                                  chooseAuthenticationApp: (
                                      self: ScreenHandle,
                                      state: LoginTwoFactorChooseMethodState,
                                  ) => ScreenEdgeBound<(...), (...)>;
                              },
                              Record<never, never>,
                              Self,
                              "required",
                          >,

                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | ScreenDataError
                              | ScreenEnterNavigationError
                              | ScreenEnterConcurrentError
                              | ScreenNotOnTopError
                              | ScreenEdgeInputError
                              | TwoFactorTicketUnknownError,
                      >,
                  >;
                  recoverPassword: (
                      self: ScreenHandle,
                      state: {
                          aac: string;
                          attestationChallengeNonce?: string;
                          loginEntryPoint: "login-home" | "landing-screen";
                          oauthQpl: { instanceId: number; markerId: number };
                          passwordTextInputId: string;
                          qpl: { instanceId: number; markerId: number };
                          recoveryTicket?: string & Brand<"RecoveryActionTicket">;
                          usernameTextInputId: string;
                          waterfallId: string;
                      },
                  ) => ScreenEdgeBound<
                      ScreenEdgeShape<
                          ScreenDefinition<
                              "AccountRecoverySearch",
                              "screen",
                              { aac: string; ticket?: string & Brand<(...)> },
                              {
                                  aac: string;
                                  searchScreenType: string;
                                  searchTicket: string & Brand<"SearchTicket">;
                                  textInputId: string;
                                  waterfallId: string;
                              },
                              {
                                  aac: string;
                                  searchScreenType: string;
                                  searchTicket: string & Brand<"SearchTicket">;
                                  textInputId: string;
                                  waterfallId: string;
                              },
                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | AccountRecoveryBloksIssuedParamsMissingError
                              | ScreenEnterNavigationError
                              | AccountRecoveryTicketUnknownError,
                              | MobileDevice
                              | MobileApplication
                              | BloksHttp
                              | MobileNavigation
                              | AccountRecoveryTickets,
                              {
                                  findAccount: (
                                      self: ScreenHandle,
                                      state: {
                                          aac: string;
                                          searchScreenType: string;
                                          searchTicket: (...) & (...);
                                          textInputId: string;
                                          waterfallId: string;
                                      },
                                  ) => ScreenEdgeBound<
                                      ScreenEdgeShape<(...), (...)>,
                                      (...args: ...) => ...,
                                  >;
                              },
                              Record<never, never>,
                              never,
                          >,
                          [],
                      >,
                      (
                          ...args: [],
                      ) => Effect<
                          AssembledHandle<
                              "AccountRecoverySearch",
                              "screen",
                              {
                                  aac: string;
                                  searchScreenType: string;
                                  searchTicket: string & Brand<(...)>;
                                  textInputId: string;
                                  waterfallId: string;
                              },
                              {
                                  findAccount: (
                                      self: ScreenHandle,
                                      state: {
                                          aac: ...;
                                          searchScreenType: ...;
                                          searchTicket: ...;
                                          textInputId: ...;
                                          waterfallId: ...;
                                      },
                                  ) => ScreenEdgeBound<(...), (...)>;
                              },
                              Record<never, never>,
                              ScreenHandle,
                              "required",
                          >,

                              | BloksHttpError
                              | InvalidSkipOrdinalsError
                              | AccountRecoveryBloksIssuedParamsMissingError
                              | ScreenDataError
                              | ScreenEnterNavigationError
                              | ScreenEnterConcurrentError
                              | AccountRecoveryTicketUnknownError
                              | ScreenNotOnTopError
                              | ScreenEdgeInputError,
                      >,
                  >;
              },
              Record<never, never>,
          >,
          AndroidAppLaunchSimulationError,
          Scope,
      >

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));
    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Parameters

      • input: AndroidAppLaunchOptionalOptions

      Returns Effect<AndroidAppLaunchFirstScreen, AndroidAppLaunchSimulationError, Scope>

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));
    • The Android app launch: one call creates the client and launches the app, and returns the first screen the app shows — the login home of a fresh install, or the feed of a process that holds the stored user. The input picks the result by type, so the caller pays for neither a dead LoginHomepageScreen.is check on a stored session nor a branch on a splash a launch never brings. There is no separate client creation and no separate launch step: the process starts and is launched here.

      The process lives in the Effect scope it was launched in, and its handle must not be used after that scope closes. The layer, the native transport included, is built in that scope, and the scope is the process's declared lifetime. The library cannot prevent a handle from being used after the scope today: the transport has no close, so the scope closes nothing, and TypeScript cannot tie a handle's use to a scope. Enforcing it is the named task process-lifetime-enforced-by-transport-close — a close on the transport's client and a layer that finalizes it with the scope. Keep every handle inside the scope that launched the process and let only its data out. A second launch of the same process is not expressible — another call is another process. To continue the session in a later program, take the screen's checkpoint() and launch again from it ({ checkpoint }), or persist a snapshot from a program over AndroidApp and launch from that.

      The returned handle is the screen: its actions (screen.logIn(…) on the login home, screen.nextPage() on the feed) run with the process's own services, so the caller does not provide the layer to them. A login on that handle continues the same process through the recorded post-login launch and returns the feed's handle; a two-factor answer returns the approval screen's handle, and a last-resort escalation the code-entry screen's. The launch itself sends what the recordings show: the pre-login bootstrap and the login home without a session, the recorded launch order and its first screen with a stored one.

      The input picks the result by type. Without a snapshot or a checkpoint the launch is a fresh install, which holds no user session: its result is the login screen alone. A checkpoint the type still names — the value screen.checkpoint() returns — picks its result by that screen (CheckpointFirstScreen): a pre-session screen (the login home the logged-out launch opens and every screen its edges reach) is a process without a session, so the launch opens the login home; any other screen is a signed-in process, so its warm launch starts on the launch declaration's first screen, StoredSessionFirstScreen (the feed), with no login home and no splash to narrow away. A snapshot's type does not say whether its process held a session, and a checkpoint that crossed JSON storage has only the wide ScreenId, so both are the honest union AndroidAppLaunchFirstScreen — as is the widened input and the caller's own optional saved-session value, which may be either at run time; the type must not allow screen.logIn(…) on a feed handle. The runtime reads the same fact: a checkpoint whose screen and session contradict each other — an in-session screen whose client holds no session, or a pre-session screen whose client holds one — is refused before the process is built, with CheckpointWithoutSessionError or CheckpointWithSessionError, and nothing is sent; it is never launched into a screen the type excludes. The location splash exists only on a launch after an account-recovery login, which is a screen action's path, never a launch's. Adding a screen to either union is a breaking change of that type. seed with a defined snapshot is refused by the type: the snapshot names the device, so the seed would be silently dropped.

      Returns Effect<AndroidAppLaunchFirstScreen, AndroidAppLaunchSimulationError, Scope>

      import { AndroidAppLaunchSimulation } from '@igpapi/android';
      import { Effect } from 'effect';

      const login = Effect.gen(function* () {
      // A fresh install's result is the login home alone: no `is` check.
      const home = yield* AndroidAppLaunchSimulation({ seed: username });
      const firstScreen = yield* home.logIn({ username, password });
      // Only the handle's data leaves the scope: the handle itself must not be
      // used after the scope that launched the process closes.
      return firstScreen.id === 'FeedTimeline' ? firstScreen.items.map(entry => entry.type) : [];
      });

      const feedEntries = await Effect.runPromise(Effect.scoped(login));