diff --git a/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs b/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs
index 856c2c0..ede3126 100644
--- a/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs
+++ b/Assets/UnityGoogleDrive/Editor/GoogleDriveSettingsEditor.cs
@@ -14,6 +14,7 @@ public class GoogleDriveSettingsEditor : Editor
private SerializedProperty uriSchemeClientCredentials;
private SerializedProperty accessScopes;
private SerializedProperty loopbackUri;
+ private SerializedProperty loopbackPort;
private SerializedProperty loopbackResponseHtml;
private SerializedProperty accessTokenPrefsKey;
private SerializedProperty refreshTokenPrefsKey;
@@ -21,7 +22,8 @@ public class GoogleDriveSettingsEditor : Editor
private static readonly GUIContent genericClientCredentialsContent = new GUIContent("Generic Credentials", "Google Drive API application credentials used to authorize requests via loopback and redirect schemes.");
private static readonly GUIContent uriSchemeClientCredentialsContent = new GUIContent("URI Scheme Credentials", "Google Drive API application credentials used to authorize requests via custom URI scheme.");
private static readonly GUIContent accessScopesContent = new GUIContent("Access Scopes", "Scopes of access to the user's Google Drive the app will request.");
- private static readonly GUIContent loopbackUriContent = new GUIContent("Loopback URI", "A web address for the loopback authentication requests. Defult is 'localhost'.");
+ private static readonly GUIContent loopbackUriContent = new GUIContent("Loopback URI", "A web address for the loopback authentication requests. Default is 'localhost'.");
+ private static readonly GUIContent loopbackPortContent = new GUIContent("Loopback Port", "Specific port to use for loopback scheme, eg: 8888. Leave empty to find random unused port.");
private static readonly GUIContent loopbackResponseHtmlContent = new GUIContent("Loopback Response HTML", "HTML page shown to the user when loopback response is received.");
private static readonly GUIContent accessTokenPrefsKeyContent = new GUIContent("Access Token Key", "PlayerPrefs key used to store access token.");
private static readonly GUIContent refreshTokenPrefsKeyContent = new GUIContent("Refresh Token Key", "PlayerPrefs key used to store refresh token.");
@@ -59,6 +61,7 @@ private void OnEnable ()
uriSchemeClientCredentials = serializedObject.FindProperty("uriSchemeClientCredentials");
accessScopes = serializedObject.FindProperty("accessScopes");
loopbackUri = serializedObject.FindProperty("loopbackUri");
+ loopbackPort = serializedObject.FindProperty("loopbackPort");
loopbackResponseHtml = serializedObject.FindProperty("loopbackResponseHtml");
accessTokenPrefsKey = serializedObject.FindProperty("accessTokenPrefsKey");
refreshTokenPrefsKey = serializedObject.FindProperty("refreshTokenPrefsKey");
@@ -76,6 +79,7 @@ public override void OnInspectorGUI ()
EditorGUILayout.PropertyField(uriSchemeClientCredentials, uriSchemeClientCredentialsContent, true);
EditorGUILayout.PropertyField(accessScopes, accessScopesContent, true);
EditorGUILayout.PropertyField(loopbackUri, loopbackUriContent);
+ EditorGUILayout.PropertyField(loopbackPort, loopbackPortContent);
EditorGUILayout.PropertyField(loopbackResponseHtml, loopbackResponseHtmlContent);
EditorGUILayout.PropertyField(accessTokenPrefsKey, accessTokenPrefsKeyContent);
EditorGUILayout.PropertyField(refreshTokenPrefsKey, refreshTokenPrefsKeyContent);
@@ -87,8 +91,7 @@ public override void OnInspectorGUI ()
using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(TargetSettings.GenericClientCredentials.ProjectId)))
if (GUILayout.Button("Manage Google Drive API app"))
- Application.OpenURL(string.Format("https://console.developers.google.com/apis/credentials?project={0}",
- TargetSettings.GenericClientCredentials.ProjectId));
+ Application.OpenURL($"https://console.developers.google.com/apis/credentials?project={TargetSettings.GenericClientCredentials.ProjectId}");
if (GUILayout.Button("Parse generic credentials JSON file..."))
ParseGenericCredentialsJson(EditorUtility.OpenFilePanel("Select Drive API app credentials JSON file", "", "json"));
diff --git a/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs b/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs
index c895eb0..da17954 100644
--- a/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs
+++ b/Assets/UnityGoogleDrive/Runtime/Authorization/LoopbackAccessTokenProvider.cs
@@ -104,7 +104,9 @@ private void ExecuteFullAuth ()
var codeChallenge = CryptoUtils.Base64UriEncodeNoPadding(codeVerifierHash);
// Creates a redirect URI using an available port on the loopback address.
- redirectUri = $"{settings.LoopbackUri}:{GetRandomUnusedPort()}";
+ var port = string.IsNullOrWhiteSpace(settings.LoopbackPort)
+ ? GetRandomUnusedPort() : int.Parse(settings.LoopbackPort);
+ redirectUri = $"{settings.LoopbackUri}:{port}";
// Listen for requests on the redirect URI.
var httpListener = new HttpListener();
@@ -113,9 +115,10 @@ private void ExecuteFullAuth ()
// Create the OAuth 2.0 authorization request.
// https://developers.google.com/identity/protocols/OAuth2WebServer#creatingclient
- var authRequest = string.Format("{0}?response_type=code&scope={1}&redirect_uri={2}&client_id={3}&state={4}&code_challenge={5}&code_challenge_method={6}" +
- "&access_type=offline" + // Forces to return a refresh token at the auth code exchange phase.
- "&approval_prompt=force", // Forces to show consent screen for each auth request. Needed to return refresh tokens on consequent auth runs.
+ var authRequest = string.Format(
+ "{0}?response_type=code&scope={1}&redirect_uri={2}&client_id={3}&state={4}&code_challenge={5}&code_challenge_method={6}" +
+ "&access_type=offline" + // Forces to return a refresh token at the auth code exchange phase.
+ "&approval_prompt=force", // Forces to show consent screen for each auth request. Needed to return refresh tokens on consequent auth runs.
settings.GenericClientCredentials.AuthUri,
Uri.EscapeDataString(settings.AccessScope),
Uri.EscapeDataString(redirectUri),
diff --git a/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs b/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs
index 471a38f..9e8ac5e 100644
--- a/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs
+++ b/Assets/UnityGoogleDrive/Runtime/GoogleDriveSettings.cs
@@ -35,6 +35,10 @@ public class GoogleDriveSettings : ScriptableObject
///
public string LoopbackUri => loopbackUri;
///
+ /// Specific port to use for loopback scheme. When empty find random unused port instead.
+ ///
+ public string LoopbackPort => loopbackPort;
+ ///
/// HTML page shown to the user when loopback response is received.
///
public string LoopbackResponseHtml { get => loopbackResponseHtml; set => loopbackResponseHtml = value; }
@@ -51,6 +55,7 @@ public class GoogleDriveSettings : ScriptableObject
[SerializeField] private UriSchemeClientCredentials uriSchemeClientCredentials;
[SerializeField] private List accessScopes = new List { "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.appdata" };
[SerializeField] private string loopbackUri = "http://localhost";
+ [SerializeField] private string loopbackPort = "";
[SerializeField] private string loopbackResponseHtml = "Please return to the app.
";
[SerializeField] private string accessTokenPrefsKey = "GoogleDriveAccessToken";
[SerializeField] private string refreshTokenPrefsKey = "GoogleDriveRefreshToken";
diff --git a/Assets/UnityGoogleDrive/package.json b/Assets/UnityGoogleDrive/package.json
index e387c36..5f2890f 100644
--- a/Assets/UnityGoogleDrive/package.json
+++ b/Assets/UnityGoogleDrive/package.json
@@ -1,6 +1,6 @@
{
"name": "com.elringus.unitygoogledrive",
- "version": "0.28.0",
+ "version": "0.29.0",
"displayName": "UnityGoogleDrive",
"description": "Google Drive SDK for Unity game engine",
"unity": "2019.4",
diff --git a/UnityGoogleDrive.unitypackage b/UnityGoogleDrive.unitypackage
index d000afb..f6cb634 100644
Binary files a/UnityGoogleDrive.unitypackage and b/UnityGoogleDrive.unitypackage differ