Skip to content

[PB-5926]: show Internxt Drive in SAF picker per session state#430

Open
terrerox wants to merge 3 commits intofeature/PB-5925-kotlin-api-clientfrom
feature/PB-5926-document-query-roots
Open

[PB-5926]: show Internxt Drive in SAF picker per session state#430
terrerox wants to merge 3 commits intofeature/PB-5925-kotlin-api-clientfrom
feature/PB-5926-document-query-roots

Conversation

@terrerox
Copy link
Copy Markdown

InternxtDocumentsProvider.queryRoots() now reads credentials from EncryptedSharedPreferences via a new InternxtAuthManager and returns an empty cursor when no user is signed in, so "Internxt Drive" only appears in the system file picker while the app has an active session; credentials are written by a new RN bridge (InternxtAuthCredentialsModule) from signInThunk and refreshTokensThunk and cleared from signOutThunk, with both paths firing contentResolver.notifyChange(rootsUri) so the picker refreshes without restarting the app, while queryRoots() itself stays strictly synchronous and local-only to avoid freezing the system picker.

@terrerox terrerox self-assigned this Apr 24, 2026
  InternxtDocumentsProvider.queryRoots() now reads credentials from EncryptedSharedPreferences via a new
   InternxtAuthManager and returns an empty cursor when no user is signed in, so "Internxt Drive" only
  appears in the system file picker while the app has an active session; credentials are written by a
  new RN bridge (InternxtAuthCredentialsModule) from signInThunk and refreshTokensThunk and cleared from
   signOutThunk, with both paths firing contentResolver.notifyChange(rootsUri) so the picker refreshes
  without restarting the app, while queryRoots() itself stays strictly synchronous and local-only to
  avoid freezing the system picker.
@terrerox terrerox force-pushed the feature/PB-5926-document-query-roots branch from 0f04755 to 5fe9130 Compare April 24, 2026 04:20
@sonarqubecloud
Copy link
Copy Markdown

@terrerox terrerox requested a review from CandelR April 24, 2026 04:43
.putString(KEY_DRIVE_BASE_URL, creds.driveBaseUrl)
.putString(KEY_BRIDGE_BASE_URL, creds.bridgeBaseUrl)
.putString(KEY_DESKTOP_TOKEN, creds.desktopToken)
.apply()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply is async, maybe for this it is preferable use commit

rootFolderUuid: user.rootFolderUuid || user.rootFolderId,
email: user.email,
driveBaseUrl: appService.constants.DRIVE_NEW_API_URL,
bridgeBaseUrl: appService.constants.BRIDGE_URL,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The desktopToken is missing

bearerToken: token,
userId: user.userId,
bridgeUser: user.bridgeUser,
rootFolderUuid: user.rootFolderUuid || user.rootFolderId,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootFolderId as fallback is wrong, remember that rootFolderId is the numeric remote database id and the rootFolderUuid is the uuid one. For api v2 we use rootFolderUuid :)

import com.internxt.cloud.BuildConfig
import com.internxt.cloud.documents.api.AuthConfig

class InternxtAuthManager(private val prefs: SharedPreferences) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this AuthManager in docuemtns instead of the other auth directory?

private lateinit var authManager: InternxtAuthManager

override fun onCreate(): Boolean {
authManager = InternxtAuthManager.create(context!!.applicationContext)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be carefull with not null assertion operator, I think this logic could lead in a unexpected bugs, better return false if there are no context

val ctx = context ?: return false
authManager = InternxtAuthManager.create(ctx.applicationContext)
return true

Comment on lines +87 to +99
fun create(context: Context): InternxtAuthManager {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
PREFS_FILE,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
return InternxtAuthManager(prefs)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is recommendable to wrap this logic in a try/catch and return null on failure so the provider degrades gracefully instead of crashing

fun create(context: Context): InternxtAuthManager? {
      return try {     
          ...
      } catch (e: GeneralSecurityException) {
          Log.e(TAG, "Keystore unavailable, SAF auth disabled", e)                                                                   
          null
      } catch (e: IOException) {                                                                                                     
          Log.e(TAG, "Could not open encrypted prefs, SAF auth disabled", e)                                                       
          null                                                                                                                       
      }                
  }                      

If implement something like the example, then we need to handle the nullable return in onCreate().
We could manage as if create() returns null, onCreate return false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants