> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# ScriptManager

The `ScriptManager` is a low-level utility for managing script resolution, downloading, and execution in React Native applications. It's particularly useful when working with code splitting, dynamic imports, and Module Federation.

:::info Why is it called ScriptManager?

It can be used to download, manage and execute external (either local or remote) JavaScript code.

:::

## Usage

```js
import { Script, ScriptManager } from "@callstack/repack/client";

ScriptManager.shared.addResolver(async (scriptId, caller) => {
  if (__DEV__) {
    return {
      url: Script.getDevServerURL(scriptId),
      cache: false,
    };
  }

  return {
    url: Script.getRemoteURL(`https://mycdn.example/assets/${scriptId}`),
  };
});

// Example usage with React.lazy and dynamic imports
const TeacherModule = React.lazy(() => import("./Teacher.js"));
const StudentModule = React.lazy(() => import("./Student.js"));

export function App({ role }) {
  if (role === "teacher") {
    return <TeacherModule />;
  }

  return <StudentModule />;
}
```

## API Reference

### ScriptManager.shared

The globally shared instance of `ScriptManager`. You should always use this instead of creating new instances.

- Type: `ScriptManager`

### addResolver

Adds a new script locator resolver to handle script resolution.

- Type: `addResolver(resolver: ScriptLocatorResolver, options?: ResolverOptions): void`
- Parameters:
  - `resolver`: Async function that resolves script location data
  - `options`: Configuration options for the resolver
    - `priority`: Priority of the resolver (default: `2`)
    - `key`: Unique key to identify the resolver

### removeResolver

Removes a previously added resolver.

- Type: `removeResolver(resolver: ScriptLocatorResolver | string): boolean`
- Parameters:
  - `resolver`: The resolver function or its unique key to remove
- Returns: `true` if resolver was found and removed, `false` otherwise

### removeAllResolvers

Removes all previously added resolvers.

- Type: `removeAllResolvers(): void`

### setStorage

Sets a storage backend for caching resolved script locator data.

- Type: `setStorage(storage: StorageApi): void`
- Parameters:
  - `storage`: Storage API implementation with `getItem` and `setItem` methods

### loadScript

Resolves, downloads, and executes a script.

- Type: `loadScript(scriptId: string, caller?: string, webpackContext?: any, referenceUrl?: string): Promise<void>`
- Parameters:
  - `scriptId`: Id of the script to load
  - `caller`: Name of the calling script (optional)
  - `webpackContext`: Webpack context (optional)
  - `referenceUrl`: Reference URL for resolution (optional)

### prefetchScript

Downloads a script without executing it.

- Type: `prefetchScript(scriptId: string, caller?: string, webpackContext?: any, referenceUrl?: string): Promise<void>`
- Parameters:
  - `scriptId`: Id of the script to prefetch
  - `caller`: Name of the calling script (optional)
  - `webpackContext`: Webpack context (optional)
  - `referenceUrl`: Reference URL for resolution (optional)

### invalidateScripts

Clears cache and removes downloaded files for given scripts.

- Type: `invalidateScripts(scriptIds?: string[]): Promise<string[]>`
- Parameters:
  - `scriptIds`: Array of script ids to invalidate (optional)
- Returns: Promise resolving to array of invalidated script ids

## Events

The `ScriptManager` extends `EventEmitter` and provides a comprehensive event system for monitoring and reacting to script loading lifecycle events. Events can be used to track script resolution, loading progress, and handle errors.

### Available Events

The following events are emitted by `ScriptManager`:

| Event Name    | Description                                                      |
| ------------- | ---------------------------------------------------------------- |
| `resolving`   | Emitted when script resolution begins                            |
| `resolved`    | Emitted when script resolution succeeds                          |
| `prefetching` | Emitted when script prefetching begins                           |
| `loading`     | Emitted when script loading begins                               |
| `loaded`      | Emitted when script loading succeeds                             |
| `error`       | Emitted when an error occurs during script resolution or loading |
| `invalidated` | Emitted when scripts are invalidated from cache                  |

### Subscribing to Events

```js
import { ScriptManager } from "@callstack/repack/client";

// Listen for script loading events
ScriptManager.shared.on("loading", (script) => {
  console.log(`Loading script: ${script.scriptId}`);
});

ScriptManager.shared.on("loaded", (script) => {
  console.log(`Successfully loaded script: ${script.scriptId}`);
});

ScriptManager.shared.on("error", (error) => {
  console.error("Script loading failed:", error);
});
```

## Hooks

The `ScriptManager` provides a hook system that allows developers to intercept and customize the script loading process at various stages. The hooks provide fine-grained control over script resolution and loading, enabling advanced use cases like custom caching strategies, script transformation, and error handling.

### Available Hooks

#### Resolution Hooks

| Hook Name       | Description                                                                |
| --------------- | -------------------------------------------------------------------------- |
| `beforeResolve` | Called before script resolution begins, allows you to modify the arguments |
| `resolve`       | Customise / override the script resolution process                         |
| `afterResolve`  | Modify the resolved script locator after resolution succeeds               |
| `errorResolve`  | Provide a fallback for resolution request when normal resolution fails     |

#### Loading Hooks

| Hook Name    | Description                                                             |
| ------------ | ----------------------------------------------------------------------- |
| `beforeLoad` | Called before script loading begins, allows you to modify the arguments |
| `load`       | Customise / override the script resolution process                      |
| `afterLoad`  | React to successful script loading event                                |
| `errorLoad`  | Provide a fallback for a script when normal loading procedure fails     |

### Using hooks

```js
ScriptManager.shared.hooks.beforeResolve(async (args) => {
  console.debug("ScriptManager.shared.hooks.beforeResolve", args);
  return args;
});

ScriptManager.shared.hooks.resolve(async (args) => {
  console.debug("ScriptManager.shared.hooks.resolve", args);
  const { scriptId, caller, referenceUrl } = args.options;
  for (const [, , resolve] of args.resolvers) {
    const locator = await resolve(scriptId, caller, referenceUrl);
    if (locator) return locator;
  }
});

ScriptManager.shared.hooks.afterResolve(async (args) => {
  console.debug("ScriptManager.shared.hooks.afterResolve", args);
  return args;
});

ScriptManager.shared.hooks.beforeLoad(async (args) => {
  console.debug("ScriptManager.shared.hooks.beforeLoad", args);
  return args;
});

ScriptManager.shared.hooks.load(async (args) => {
  console.debug("ScriptManager.shared.hooks.load", args);
  await args.loadScript();
  return true;
});

ScriptManager.shared.hooks.afterLoad(async (args) => {
  console.debug("ScriptManager.shared.hooks.afterLoad", args);
  return args;
});
```

## Advanced Usage

### Custom Resolver with Retry Logic

```js
import { ScriptManager } from "@callstack/repack/client";

ScriptManager.shared.addResolver(async (scriptId) => {
  return {
    url: `https://mycdn.example/assets/${scriptId}`,
    retry: 3, // Number of retry attempts
    retryDelay: 1000, // Delay between retries in milliseconds
    headers: {
      Authorization: "Bearer token",
    },
  };
});
```

### Code signing with a per-script public key

When different teams sign remote bundles with different private keys, your resolver can provide the matching public key for each script. Re.Pack will use `publicKey` when present and fall back to the app-embedded `RepackPublicKey` only when it's omitted.

```js
import { ScriptManager } from "@callstack/repack/client";

ScriptManager.shared.addResolver(async (scriptId) => {
  const metadata = await fetch(`https://myapp.example/scripts/${scriptId}`).then(
    (response) => response.json()
  );

  return {
    url: metadata.bundleUrl,
    verifyScriptSignature: "strict",
    publicKey: metadata.publicKey,
  };
});
```

Only use a `publicKey` value that comes from a trusted source. If both the bundle and the public key can be tampered with by the same attacker, signature verification no longer protects the download.

### Enabling caching through AsyncStorage

```js
import { ScriptManager } from "@callstack/repack/client";
import AsyncStorage from "@react-native/async-storage";

ScriptManager.shared.setStorage({
  getItem: (key) => AsyncStorage.getItem(key),
  setItem: (key, value) => AsyncStorage.setItem(key, value),
});
```

### Using per-script caching strategy

```js
ScriptManager.shared.hooks.afterResolve(async (args) => {
  const { locator, options } = args;
  // Implement custom caching logic
  if (shouldCache(locator)) {
    locator.cache = true;
  }
  return args;
});
```

### Override script locator URL after resolution

```js
ScriptManager.shared.hooks.afterResolve(async (args) => {
  const { locator, options } = args;

  // Transform locator URL after it's resolved
  locator.url = transformUrl(locator.url);

  return args;
});
```

### Adding fallback for failed loading of a script

```js
ScriptManager.shared.hooks.errorLoad(async (args) => {
  const { error, options } = args;
  // Implement custom error handling
  if (isRecoverableError(error)) {
    await retryLoading(options);
    return true;
  }
  return false;
});
```

## Customizing the native HTTP client

Remote scripts are downloaded by the native side of Re.Pack - `OkHttpClient` on Android and `NSURLSession` on iOS. By default a plain client is used, but you can provide your own to customize networking behavior such as **SSL/certificate pinning**, **interceptors**, **proxies**, **custom headers**, or **timeouts**.

The factory must be set **before any remote script is loaded** - the earliest app lifecycle hook is the safest place (Android `Application.onCreate`, iOS `application:didFinishLaunchingWithOptions:`). The client is created lazily and reused for all subsequent downloads.

:::warning Native customization only

This applies to the native HTTP client used for downloading scripts. Per-request options exposed to JavaScript (such as `headers`, `method`, `body`, `timeout` and `retry`) are still configured through the [resolver](#addresolver).

:::

### Android

Assign a factory to `RemoteScriptLoader.okHttpClientFactory`:

```kotlin
// MainApplication.kt
import com.callstack.repack.RemoteScriptLoader
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

class MainApplication : Application(), ReactApplication {
  override fun onCreate() {
    super.onCreate()

    RemoteScriptLoader.okHttpClientFactory = {
      OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .addInterceptor(MyAuthInterceptor())
        // .certificatePinner(...) for SSL pinning
        .build()
    }
    // ...
  }
}
```

### iOS

Assign a factory to `ScriptManager.urlSessionFactory`. Assign `nil` to restore the default `[NSURLSession sharedSession]`.

```swift
// AppDelegate.swift
import callstack_repack

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  ScriptManager.urlSessionFactory = {
    let configuration = URLSessionConfiguration.default
    configuration.timeoutIntervalForRequest = 30
    configuration.httpAdditionalHeaders = ["X-Custom-Header": "value"]
    // Pass a delegate for SSL pinning if needed.
    return URLSession(configuration: configuration)
  }
  // ...
}
```

```objc
// AppDelegate.mm
#import <callstack_repack/callstack_repack-Swift.h> // or "ScriptManager.h"

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ScriptManager.urlSessionFactory = ^NSURLSession * {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.timeoutIntervalForRequest = 30;
    return [NSURLSession sessionWithConfiguration:configuration];
  };
  // ...
}
```

## Related

- [Script](/api/runtime/script.md) - Utility class for generating script URLs used with resolvers
