Skip to content

Commit

Permalink
Use a stable device+app ID to register with the inspector proxy
Browse files Browse the repository at this point in the history
Summary:
Building on byCedric's approach in facebook/metro#991, and on D49954920, this diff passes stable, unique *logical device IDs* to the debugger connection infrastructure from Android and iOS.

See D49954920 for the precise stability and uniqueness requirements that these IDs meet.

Changelog:

[Changed][General] - Automatically reconnect to an existing debugger session on relaunching the app

Reviewed By: huntie

Differential Revision: D49954919

fbshipit-source-id: 5796209efd0104f482d2a98c2616a163ea67ea61
  • Loading branch information
motiz88 authored and facebook-github-bot committed Oct 23, 2023
1 parent 8ec3026 commit 3e8905c
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#import <React/RCTDefines.h>
#import <React/RCTInspectorPackagerConnection.h>

#import <CommonCrypto/CommonCrypto.h>

static NSString *const kDebuggerMsgDisable = @"{ \"id\":1,\"method\":\"Debugger.disable\" }";

static NSString *getServerHost(NSURL *bundleURL)
Expand All @@ -40,16 +42,65 @@
return [NSString stringWithFormat:@"%@:%@", host, port];
}

static NSString *getSHA256(NSString *string)
{
const char *str = string.UTF8String;
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(str, (CC_LONG)strlen(str), result);

return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0],
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
result[7],
result[8],
result[9],
result[10],
result[11],
result[12],
result[13],
result[14],
result[15],
result[16],
result[17],
result[18],
result[19]];
}

// Returns an opaque ID which is stable for the current combination of device and app, stable across installs,
// and unique across devices.
static NSString *getInspectorDeviceId()
{
// A bundle ID uniquely identifies a single app throughout the system. [Source: Apple docs]
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];

// An alphanumeric string that uniquely identifies a device to the app's vendor. [Source: Apple docs]
NSString *identifierForVendor = [[UIDevice currentDevice] identifierForVendor].UUIDString;

NSString *rawDeviceId = [NSString stringWithFormat:@"apple-%@-%@", identifierForVendor, bundleId];

return getSHA256(rawDeviceId);
}

static NSURL *getInspectorDeviceUrl(NSURL *bundleURL)
{
NSString *escapedDeviceName = [[[UIDevice currentDevice] name]
stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
NSString *escapedAppName = [[[NSBundle mainBundle] bundleIdentifier]
stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/inspector/device?name=%@&app=%@",

NSString *escapedInspectorDeviceId = [getInspectorDeviceId()
stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];

return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/inspector/device?name=%@&app=%@&device=%@",
getServerHost(bundleURL),
escapedDeviceName,
escapedAppName]];
escapedAppName,
escapedInspectorDeviceId]];
}

@implementation RCTInspectorDevServerHelper
Expand All @@ -70,8 +121,13 @@ + (void)openDebugger:(NSURL *)bundleURL withErrorMessage:(NSString *)errorMessag
NSString *appId = [[[NSBundle mainBundle] bundleIdentifier]
stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];

NSURL *url = [NSURL
URLWithString:[NSString stringWithFormat:@"http://%@/open-debugger?appId=%@", getServerHost(bundleURL), appId]];
NSString *escapedInspectorDeviceId = [getInspectorDeviceId()
stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];

NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/open-debugger?appId=%@&device=%@",
getServerHost(bundleURL),
appId,
escapedInspectorDeviceId]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import android.net.Uri;
import android.os.AsyncTask;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
Expand All @@ -30,6 +31,9 @@
import com.facebook.react.util.RNLog;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -244,13 +248,73 @@ public String getWebsocketProxyURL() {
mPackagerConnectionSettings.getDebugServerHost());
}

private static String getSHA256(String string) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Could not get standard SHA-256 algorithm", e);
}
digest.reset();
byte[] result;
try {
result = digest.digest(string.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new AssertionError("This environment doesn't support UTF-8 encoding", e);
}
return String.format(
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0],
result[1],
result[2],
result[3],
result[4],
result[5],
result[6],
result[7],
result[8],
result[9],
result[10],
result[11],
result[12],
result[13],
result[14],
result[15],
result[16],
result[17],
result[18],
result[19]);
}

// Returns an opaque ID which is stable for the current combination of device and app, stable
// across installs, and unique across devices.
private String getInspectorDeviceId() {
// Every Android app has a unique application ID that looks like a Java or Kotlin package name,
// such as com.example.myapp. This ID uniquely identifies your app on the device and in the
// Google Play Store.
// [Source: Android docs]
String packageName = mPackageName;

// A 64-bit number expressed as a hexadecimal string, which is either:
// * unique to each combination of app-signing key, user, and device (API level >= 26), or
// * randomly generated when the user first sets up the device and should remain constant for
// the lifetime of the user's device (API level < 26).
// [Source: Android docs]
String androidId = Settings.Secure.ANDROID_ID;

String rawDeviceId = String.format(Locale.US, "android-%s-%s", packageName, androidId);

return getSHA256(rawDeviceId);
}

private String getInspectorDeviceUrl() {
return String.format(
Locale.US,
"http://%s/inspector/device?name=%s&app=%s",
"http://%s/inspector/device?name=%s&app=%s&device=%s",
mPackagerConnectionSettings.getInspectorServerHost(),
AndroidInfoHelpers.getFriendlyDeviceName(),
mPackageName);
Uri.encode(AndroidInfoHelpers.getFriendlyDeviceName()),
Uri.encode(mPackageName),
Uri.encode(getInspectorDeviceId()));
}

public void downloadBundleFromURL(
Expand Down Expand Up @@ -425,9 +489,10 @@ public void openDebugger(final ReactContext context, final String errorMessage)
String requestUrl =
String.format(
Locale.US,
"http://%s/open-debugger?appId=%s",
"http://%s/open-debugger?appId=%s&device=%s",
mPackagerConnectionSettings.getInspectorServerHost(),
Uri.encode(mPackageName));
Uri.encode(mPackageName),
Uri.encode(getInspectorDeviceId()));
Request request =
new Request.Builder().url(requestUrl).method("POST", RequestBody.create(null, "")).build();

Expand Down

0 comments on commit 3e8905c

Please sign in to comment.