Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.revyl.com/llms.txt

Use this file to discover all available pages before exploring further.

Auth bypass deep links let Revyl start a test from a signed-in app state without automating every login screen. The easiest implementation is:
  1. Add a test-only deep link handler in your app.
  2. Gate that handler with Revyl launch variables.
  3. Open one deep link from revyl device navigate or a YAML navigate step.
Only enable this in simulator, staging, debug, or explicit test builds. Do not ship a permanent auth bypass in production.

Fastest Path For Expo Cloud Agents

Use this path when you already run an Expo development client through revyl dev.
npx expo start --tunnel --dev-client

revyl dev --no-build --tunnel "<expo-dev-client-link>" \
  --launch-var REVYL_AUTH_BYPASS_ENABLED \
  --launch-var REVYL_AUTH_BYPASS_TOKEN

revyl device navigate \
  --url "myapp://revyl-auth?token=$REVYL_AUTH_BYPASS_TOKEN&role=buyer&redirect=%2Fcheckout"
Load the Expo dev client project first, then send the app-specific auth deep link. In managed Expo apps, JavaScript may not automatically see native launch values; use a small native config bridge, verify the token with a staging backend, or use a local demo fallback for sample apps only.

Copy This Contract

Use one route shape across every framework:
myapp://revyl-auth?token=<token>&role=<role>&redirect=<allowlisted-route>
Create these launch variables:
revyl global launch-var create REVYL_AUTH_BYPASS_ENABLED=true
revyl global launch-var create REVYL_AUTH_BYPASS_TOKEN=revyl-demo-token
Then start a device with the variables attached and open the bypass link:
revyl device start \
  --platform ios \
  --launch-var REVYL_AUTH_BYPASS_ENABLED \
  --launch-var REVYL_AUTH_BYPASS_TOKEN

revyl device navigate \
  --url "myapp://revyl-auth?token=revyl-demo-token&role=buyer&redirect=%2Fcheckout"
Use the same URL in YAML tests:
test:
  metadata:
    name: "Auth bypass to checkout"
    platform: ios

  build:
    name: "Shopping App"

  env_vars:
    - REVYL_AUTH_BYPASS_ENABLED
    - REVYL_AUTH_BYPASS_TOKEN

  variables:
    auth_bypass_token: revyl-demo-token

  blocks:
    - type: manual
      step_type: navigate
      step_description: "myapp://revyl-auth?token={{auth_bypass_token}}&role=buyer&redirect=%2Fcheckout"

    - type: validation
      step_description: "The checkout screen is visible and the user is signed in"

App Checklist

Your app owns four small pieces:
  • Read REVYL_AUTH_BYPASS_ENABLED and REVYL_AUTH_BYPASS_TOKEN at app launch.
  • Register a handler for myapp://revyl-auth.
  • Verify the token, role, and redirect before changing app state.
  • Create a test-only session and route only to an allowlisted destination.
Invalid links should fail visibly in test builds. Show a debug banner, account-screen status, toast, or log line that says why the link was rejected.

Framework Recipes

Pick the recipe closest to your app. The session creation and route names are app-specific, but the Revyl contract stays the same.
Put the handler near your root layout so it catches initial links and links opened while the app is running.
import { useEffect } from "react";
import * as Linking from "expo-linking";
import { router } from "expo-router";

const allowedRedirects = new Map([
  ["/account", "/(tabs)/account"],
  ["/cart", "/cart"],
  ["/checkout", "/checkout"],
]);
const allowedRoles = new Set(["buyer", "support"]);

function launchValue(key: string) {
  return process.env[key];
}

function handleRevylAuthBypass(rawURL: string) {
  const url = new URL(rawURL);
  if (url.protocol !== "myapp:" || url.hostname !== "revyl-auth") return;

  const enabled = launchValue("REVYL_AUTH_BYPASS_ENABLED") === "true";
  const expectedToken = launchValue("REVYL_AUTH_BYPASS_TOKEN");
  const token = url.searchParams.get("token");
  const role = url.searchParams.get("role") || "buyer";
  const redirect = url.searchParams.get("redirect") || "/account";
  const route = allowedRedirects.get(redirect);

  if (!enabled) throw new Error("Revyl auth bypass is disabled");
  if (!expectedToken || token !== expectedToken) throw new Error("Bad Revyl auth bypass token");
  if (!allowedRoles.has(role)) throw new Error("Role is not allowlisted");
  if (!route) throw new Error("Redirect is not allowlisted");

  createTestSession({ role });
  router.replace(route);
}

export function useRevylAuthBypass() {
  useEffect(() => {
    Linking.getInitialURL().then(url => {
      if (url) handleRevylAuthBypass(url);
    });

    const subscription = Linking.addEventListener("url", event => {
      handleRevylAuthBypass(event.url);
    });

    return () => subscription.remove();
  }, []);
}
In managed Expo apps, JavaScript may not receive native launch values automatically. Use a small native config bridge, verify the token with a staging backend, or follow the Bug Bazaar sample’s demo fallback for a local fixture only.For Expo Router apps, add a route backstop such as app/revyl-auth.tsx that calls the same handler from route params. Expo Router may treat myapp://revyl-auth?... as a normal app route while the development client is already running; the route backstop keeps rejected links visible instead of landing on an unmatched-route screen.
Use React Native Linking for the deep link and read launch config from your native layer or staging backend.
import { useEffect } from "react";
import { Linking, NativeModules } from "react-native";

const allowedRedirects = new Set(["/account", "/checkout", "/cart"]);
const allowedRoles = new Set(["buyer", "support"]);

async function getLaunchConfig() {
  return NativeModules.LaunchConfig.getRevylAuthBypassConfig();
  // { enabled: boolean, token: string }
}

async function handleRevylAuthBypass(rawURL: string) {
  const url = new URL(rawURL);
  if (url.protocol !== "myapp:" || url.hostname !== "revyl-auth") return;

  const config = await getLaunchConfig();
  const token = url.searchParams.get("token");
  const role = url.searchParams.get("role") || "buyer";
  const redirect = url.searchParams.get("redirect") || "/account";

  if (!config.enabled) throw new Error("Revyl auth bypass is disabled");
  if (token !== config.token) throw new Error("Bad Revyl auth bypass token");
  if (!allowedRoles.has(role)) throw new Error("Role is not allowlisted");
  if (!allowedRedirects.has(redirect)) throw new Error("Redirect is not allowlisted");

  await createTestSession({ role });
  navigationRef.navigate(routeNameForRedirect(redirect));
}

export function useRevylAuthBypass() {
  useEffect(() => {
    Linking.getInitialURL().then(url => {
      if (url) void handleRevylAuthBypass(url);
    });

    const subscription = Linking.addEventListener("url", event => {
      void handleRevylAuthBypass(event.url);
    });

    return () => subscription.remove();
  }, []);
}
For iOS, expose Revyl launch variables from ProcessInfo.processInfo.arguments. For Android, expose them from the launch Intent extras.
Revyl injects launch variables into iOS simulator apps as launch arguments: -REVYL_AUTH_BYPASS_ENABLED true -REVYL_AUTH_BYPASS_TOKEN <token>.
import SwiftUI

private let allowedRedirects: [String: AppRoute] = [
    "/account": .account,
    "/checkout": .checkout,
    "/cart": .cart
]
private let allowedRoles: Set<String> = ["buyer", "support"]

func launchValue(_ key: String) -> String? {
    let args = ProcessInfo.processInfo.arguments
    guard let index = args.firstIndex(of: "-\(key)") else { return nil }
    let valueIndex = args.index(after: index)
    return args.indices.contains(valueIndex) ? args[valueIndex] : nil
}

func queryValue(_ name: String, in url: URL) -> String? {
    let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? []
    return items.first(where: { $0.name == name })?.value
}

func handleRevylAuthBypass(_ url: URL) throws {
    guard url.scheme == "myapp", url.host == "revyl-auth" else { return }

    guard launchValue("REVYL_AUTH_BYPASS_ENABLED") == "true" else { throw AuthBypassError.disabled }
    guard queryValue("token", in: url) == launchValue("REVYL_AUTH_BYPASS_TOKEN") else { throw AuthBypassError.badToken }

    let role = queryValue("role", in: url) ?? "buyer"
    let redirect = queryValue("redirect", in: url) ?? "/account"
    guard allowedRoles.contains(role) else { throw AuthBypassError.badRole }
    guard let route = allowedRedirects[redirect] else { throw AuthBypassError.badRedirect }

    TestSession.signIn(role: role)
    AppRouter.shared.navigate(to: route)
}
Call handleRevylAuthBypass(_:) from your SwiftUI .onOpenURL handler or your app delegate URL handler.
Revyl injects launch variables into Android as string extras on the app launch intent.
private val allowedRedirects = mapOf(
  "/account" to AppRoute.Account,
  "/checkout" to AppRoute.Checkout,
  "/cart" to AppRoute.Cart,
)
private val allowedRoles = setOf("buyer", "support")

data class RevylAuthConfig(val enabled: Boolean, val token: String?)

fun revylAuthConfig(intent: Intent): RevylAuthConfig {
  return RevylAuthConfig(
    enabled = intent.getStringExtra("REVYL_AUTH_BYPASS_ENABLED") == "true",
    token = intent.getStringExtra("REVYL_AUTH_BYPASS_TOKEN"),
  )
}

fun handleRevylAuthBypass(intent: Intent, config: RevylAuthConfig) {
  val uri = intent.data ?: return
  if (uri.scheme != "myapp" || uri.host != "revyl-auth") return

  val role = uri.getQueryParameter("role") ?: "buyer"
  val redirect = uri.getQueryParameter("redirect") ?: "/account"

  check(config.enabled) { "Revyl auth bypass is disabled" }
  check(uri.getQueryParameter("token") == config.token) { "Bad Revyl auth bypass token" }
  check(role in allowedRoles) { "Role is not allowlisted" }

  val route = allowedRedirects[redirect] ?: error("Redirect is not allowlisted")
  TestSession.signIn(role)
  appRouter.navigate(route)
}
Store revylAuthConfig(launchIntent) when the app starts, then call handleRevylAuthBypass(intent, config) from onCreate and onNewIntent.
Flutter apps usually handle the link in Dart and read launch config through a platform channel or staging backend.
import 'package:app_links/app_links.dart';
import 'package:flutter/services.dart';

const launchConfig = MethodChannel('app.launchConfig');
final allowedRedirects = {
  '/account': AppRoute.account,
  '/checkout': AppRoute.checkout,
  '/cart': AppRoute.cart,
};
final allowedRoles = {'buyer', 'support'};

Future<void> handleRevylAuthBypass(Uri uri) async {
  if (uri.scheme != 'myapp' || uri.host != 'revyl-auth') return;

  final config = await launchConfig.invokeMapMethod<String, String>('revylAuthBypass');
  final enabled = config?['REVYL_AUTH_BYPASS_ENABLED'] == 'true';
  final expectedToken = config?['REVYL_AUTH_BYPASS_TOKEN'];
  final role = uri.queryParameters['role'] ?? 'buyer';
  final redirect = uri.queryParameters['redirect'] ?? '/account';

  if (!enabled) throw StateError('Revyl auth bypass is disabled');
  if (uri.queryParameters['token'] != expectedToken) throw StateError('Bad Revyl auth bypass token');
  if (!allowedRoles.contains(role)) throw StateError('Role is not allowlisted');

  final route = allowedRedirects[redirect];
  if (route == null) throw StateError('Redirect is not allowlisted');

  await TestSession.signIn(role: role);
  appRouter.go(route);
}

Future<void> installRevylAuthBypass() async {
  final links = AppLinks();
  final initial = await links.getInitialLink();
  if (initial != null) await handleRevylAuthBypass(initial);
  links.uriLinkStream.listen(handleRevylAuthBypass);
}
Implement the platform channel with the native iOS and Android launch-config snippets above, or verify the token against your staging backend from Dart.

What To Verify

Before relying on the bypass in a test, confirm each case is visible:
  • Valid token, role, and redirect signs in and routes to the target screen.
  • Wrong token is rejected.
  • Missing or disabled REVYL_AUTH_BYPASS_ENABLED is rejected.
  • Unknown role is rejected.
  • Unknown redirect is rejected.
  • The handler is unavailable in production builds.

Bug Bazaar Sample

This repo includes the runnable Expo sample in internal-apps/bug-bazaar:
revyl device navigate \
  --url "bug-bazaar://revyl-auth?token=revyl-demo-token&role=collector&redirect=%2Fcheckout"
The Account tab shows whether the auth bypass link is idle, accepted, or rejected. Bug Bazaar is a managed Expo sample, so it uses a demo fallback token when no native launch-config bridge is present. Customer apps should wire REVYL_AUTH_BYPASS_TOKEN into native launch config or verify the token with a staging backend.