Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-buttons-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@callstack/repack": minor
---

Allow customizing the native HTTP client used to download remote scripts: `RemoteScriptLoader.okHttpClientFactory` on Android and `ScriptManager.urlSessionFactory` on iOS (for SSL pinning, interceptors, custom headers, timeouts, etc.)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,19 @@ import java.util.concurrent.TimeUnit

class RemoteScriptLoader(val reactContext: ReactContext, private val nativeLoader: NativeScriptLoader) {
private val scriptsDirName = "scripts"
private val client = OkHttpClient()
private val client: OkHttpClient by lazy(okHttpClientFactory)

companion object {
/**
* Factory used to create the [OkHttpClient] for downloading remote scripts.
*
* Set this before any remote script is loaded (e.g. in your Application's
* onCreate) to provide a custom client - for SSL pinning, interceptors,
* proxies, timeouts, etc. Defaults to a plain `OkHttpClient()`. This is the
* Android counterpart of `ScriptManager.urlSessionFactory` on iOS.
*/
var okHttpClientFactory: () -> OkHttpClient = { OkHttpClient() }
}

private fun getScriptFilePath(scriptUniqueId: String): String {
return "${scriptsDirName}/$scriptUniqueId.script.bundle"
Expand Down
11 changes: 11 additions & 0 deletions packages/repack/ios/ScriptManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,15 @@
@interface ScriptManager : NSObject <RCTBridgeModule>
#endif

/**
* Factory used to create the `NSURLSession` for downloading remote scripts.
*
* Set this before any remote script is loaded (e.g. in your AppDelegate) to
* provide a custom session - for SSL pinning, custom headers, proxies, timeouts,
* etc. Defaults to `[NSURLSession sharedSession]`. Assign `nil` to restore the
* default. This is the iOS counterpart of `RemoteScriptLoader.okHttpClientFactory`
* on Android.
*/
@property (class, nonatomic, copy, null_resettable) NSURLSession * (^urlSessionFactory)(void);

@end
32 changes: 31 additions & 1 deletion packages/repack/ios/ScriptManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,42 @@ @interface RCTBridge (RCTTurboModule)
@end
#endif

static NSURLSession * (^_urlSessionFactory)(void) = nil;
static NSURLSession *_cachedURLSession = nil;

@implementation ScriptManager

RCT_EXPORT_MODULE()

@synthesize bridge = _bridge;

+ (NSURLSession * (^)(void))urlSessionFactory
{
if (_urlSessionFactory == nil) {
_urlSessionFactory = ^NSURLSession * {
return [NSURLSession sharedSession];
};
}
return _urlSessionFactory;
}

+ (void)setUrlSessionFactory:(NSURLSession * (^)(void))urlSessionFactory
{
_urlSessionFactory = [urlSessionFactory copy];
// Invalidate the cached session so the next download picks up the new factory.
_cachedURLSession = nil;
}

// Lazily build (and cache) the session from the factory, mirroring Android's
// `OkHttpClient by lazy(okHttpClientFactory)`.
- (NSURLSession *)urlSession
{
if (_cachedURLSession == nil) {
_cachedURLSession = ScriptManager.urlSessionFactory();
}
return _cachedURLSession;
}

#ifdef RCT_NEW_ARCH_ENABLED
RCT_EXPORT_METHOD(loadScript
: (nonnull NSString *)scriptId scriptConfig
Expand Down Expand Up @@ -235,7 +265,7 @@ - (void)downloadAndCache:(ScriptConfig *)config completionHandler:(void (^)(NSEr
[request setValue:@"text/plain" forHTTPHeaderField:@"content-type"];
}

NSURLSessionDataTask *task = [[NSURLSession sharedSession]
NSURLSessionDataTask *task = [self.urlSession
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
Expand Down
77 changes: 77 additions & 0 deletions website/src/latest/api/runtime/script-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,83 @@ ScriptManager.shared.hooks.errorLoad(async (args) => {
});
```

## 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) - Utility class for generating script URLs used with resolvers
Loading