Skip to content

Release 1.11.0- Native file-system integration - #533

Open
terrerox wants to merge 80 commits into
masterfrom
release-1.9.1
Open

Release 1.11.0- Native file-system integration#533
terrerox wants to merge 80 commits into
masterfrom
release-1.9.1

Conversation

@terrerox

@terrerox terrerox commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Release 1.11.0 — Native file-system integration (Android SAF + iOS File Provider)

This release makes Internxt Drive available directly in the operating system's file pickers and file managers on both platforms, with end-to-end encryption preserved throughout.

Android — Storage Access Framework (DocumentsProvider)

  • New InternxtDocumentsProvider that exposes Drive in the system file picker and Files apps, shown/hidden based on session state.
  • Browse: list folder contents with document row caching, MIME type resolution, and UUID-encoded document IDs.
  • Download & open: files are downloaded and decrypted natively (new InternxtApiClient using the /info download-links endpoint, FileKeyDeriver backed by CryptoService, dedicated HTTP clients with coroutine-based IO).
  • Upload: full encrypted upload pipeline from createDocument, including multipart uploads, a foreground service with progress notifications and cancel support, and picker refresh afteion.
  • File management: create folders, rename, move, and trash directly from the SAF picker.
  • Auth bridge: new InternxtAuthCredentialsModule syncs credentials (including mnemonic, root folder UUID, and client name/version) from
    React Native to the native layer.
  • Dependency bumps: OkHttp 5.3.2, security-crypto 1.1.0.

iOS — File Provider extension (Files app)

  • New InternxtFileProvider extension target (replicated File Provider) wired into the Xcode project, with domain registration/removal on login/logout (PB-5920).
  • Browse: enumerate Drive folders in Files.app via tholution through item(for:).
  • Download & open: files are downloaded and decrypted on demand when opened from Files.
  • Upload & folders: create folders and upload encrypt
  • File management: rename, move, and trash support, with errors mapped to standard NSFileProviderError. - Live refresh: working-set change-feed signaling (`Shot/diff) so Files.app reflects changes made elsewhere; RNdrive mutations (uploads, folder creation, rename) now signal the File Provider.
  • Shared credentials: SharedAuthKeychain extended to JSON-encoded payloads with background-read accessibility, forwarding driveBaseUrl and theme preference to the extension via the App Group.
  • Extension refactored into focused services (enumeration, download, upload, mutation) for testability.

Cross-platform / React Native

  • New InternxtSignalingModule and InternxtAuthCredentialsModule JS wrappers; auth slice syncs native credentials on login/logout.
  • Drive file/folder services signal the native layer afte
  • Assorted fixes: encryption version format in CreateFileEntry, openDocument exception handling, ETag handling in multipart uploads.

@terrerox
terrerox marked this pull request as draft July 13, 2026 17:37
Comment thread android/app/src/main/AndroidManifest.xml Fixed
Comment thread ios/Internxt/SharedAuthKeychain.swift Fixed
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

@terrerox terrerox self-assigned this Jul 15, 2026
android:authorities="com.internxt.cloud.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
kSecValueData as String: value,
]
SecItemAdd(query as CFDictionary, nil)
@terrerox
terrerox marked this pull request as ready for review August 23, 2026 05:22

@CandelR CandelR left a comment

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.

Hey @terrerox , have a look at the SonarCloud issues and the CI tests. Please also add a description of what’s included in this release :)

@terrerox terrerox changed the title Release 1.9.1 Release 1.9.1- Native file-system integration (Android SAF + iOS File Provider) Aug 24, 2026
@terrerox terrerox changed the title Release 1.9.1- Native file-system integration (Android SAF + iOS File Provider) Release 1.9.1- Native file-system integration Aug 24, 2026
@terrerox terrerox changed the title Release 1.9.1- Native file-system integration Release 1.11.0- Native file-system integration Aug 24, 2026
Comment thread android/app/src/main/AndroidManifest.xml Fixed
@terrerox

Copy link
Copy Markdown
Contributor Author

Quality Gate Failed Quality Gate failed

Failed conditions C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

  1. AndroidManifest.xml (S6361): wants separate read/write permissions on the provider, but Android forces DocumentsProviders to use MANAGE_DOCUMENTS for both. I even tried splitting it into readPermission/writePermission and Sonar still complains, since it's the same permission either way. Not a real issue anyway, only the OS itself can hold that permission.

  2. SharedAuthKeychain.swift: asks why the item doesn't require Face ID/passcode. It can't, the File Provider extension reads those credentials in the background with no UI, so there's nowhere to show a prompt. Adding auth there would just break the Files.app integration. The items are still locked to our signed apps only and encrypted until first unlock.

Adds a pure-Kotlin HTTP client that talks directly to the Drive and Bridge APIs so the SAF DocumentsProvider can fetch folder contents and download links without the React Native bridge. Covers list/create/rename/move/trash on Drive (Bearer auth) and getDownloadLinks on Bridge (Basic auth with sha256(userId).hex derivation). Typed errors for 401/404/other/network; MockWebServer tests for parsing, auth headers, gateway-required headers, and error mapping.
  - Wire AuthConfig.clientName/clientVersion through
    BuildConfig.INTERNXT_CLIENT_NAME and BuildConfig.INTERNXT_CLIENT_VERSION,
    populated by app/build.gradle from package.json. package.json is now the
    single source of truth for the internxt-client/internxt-version headers.
  - Extract JSON helpers (orEmpty, map, optStringOrNull, optLongFlexible)
    from InternxtApiClient into JsonExtensions.kt; cover each branch in a
    new JsonExtensionsTest.
  - Rename InternxtApiClient.execute -> executeApiRequest.
  - Expand InternxtApiClientTest: listFolderFolders, createFolder, null
    optional fields, size given as a string, and 5xx -> ApiError. Add an
    enqueueJson helper to cut response-stub boilerplate.
  - Add @TamaraFinogina as CODEOWNER for the new documents/crypto/ directory.
  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.
  - Implement queryChildDocuments to paginate folders + files for a parent UUID
  - Implement queryDocument to resolve real metadata via folders/{uuid}/meta
    and files/{uuid}/meta, with a UUID-named placeholder fallback for
    offline / transient failures
  - Add InternxtApiClient.getFolder / getFile (404 → null) plus tests
  - Introduce DocumentRowBuilder (column-keyed rows) and MimeTypes
    (hand-rolled table + Android MimeTypeMap fallback) with tests
  - Surface InternxtApiException + null auth-config via Log.w so silent
    failures are no longer invisible
  - Fix auth slice to sync newToken (not token) to native credentials so
    the documents provider authenticates against drive endpoints
Replace the placeholder enumerator with real Drive folder browsing:
paginate folders then files for a container, build FileProviderItem
metadata (name, type, size, dates, capabilities) from Drive responses,
and resolve identifiers via a typed folder/file id codec. Add a
DriveAPIFactory that constructs an authenticated DriveAPI from the
shared App Group keychain, and wire the new sources into the
extension target.
Implement item(for:) so the system can resolve any identifier to a
FileProviderItem: return the root container item, decode folder/file
identifiers and fetch their metadata from the Drive API, and map 401
responses to NSFileProviderError.notAuthenticated so the system can
re-request credentials.
Persist the Drive API base URL in the App Group keychain when
credentials are written, so the File Provider extension can build
authenticated Drive requests. Add a wrapper test asserting the native
module receives driveBaseUrl.
Add FileProviderDomainManager.signalEnumeration to notify the working
set enumerator, and call it once the File Provider domain is registered
so the Files app refreshes Drive contents right after credentials
change. Signal failures are logged and ignored to avoid blocking login.
Adds NetworkFacadeFactory and SharedKeychainCredentials to the File Provider
extension so items can be downloaded from the network and decrypted on demand.
Persists bridgeBaseUrl to the shared keychain and JSON-decodes bridgeUser/userId
so the extension reads matching credentials.
Implement the write path in the NSFileProviderReplicatedExtension so the
Files.app can create folders and upload files to Drive. createItem routes
folder creation to the Drive API and file uploads through InternxtSwiftCore
encryption plus network upload, then registers the file and returns the
created NSFileProviderItem. Adds rename support via modifyItem and friendly
non-crashing errors for offline/unauthenticated/generic failures.
…FileProviderError

Revert the FriendlyError helper and fileProviderError factory; surface
plain NSFileProviderError values while keeping the same error codes
(noSuchItem, notAuthenticated, serverUnreachable).
Extract upload, download, mutation, and error-mapping logic out of the
monolithic FileProviderExtension into dedicated services so the extension
becomes a thin coordinator, easing testability and maintenance ahead of
move/trash/versioning work.
Introduce a host-less InternxtFileProviderTests target covering the pure
NSFileProviderError mapping logic now isolated in FileProviderErrorMapper.
Implement modifyItem move/rename and deleteItem-to-backend-trash in the
File Provider extension. Item capabilities use .allowsDeleting (not
.allowsTrashing) so the system routes deletes through deleteItem, which
maps to the Drive backend trash endpoint instead of a local-only trash.
Add a per-folder snapshot/diff change-feed in the File Provider
extension so working-set enumeration surfaces upload, create-folder,
move, rename, trash and restore as changes. SyncAnchorStore persists
the per-domain anchor; FileProviderModule exposes a native bridge so
the host app can signal the enumerator on RN mutations.
Add a cross-platform InternxtSignalingModule with a single
notifyParentChanged(parentUuid) bridge (symmetric to Android) and call
it from the upload, create-folder, move, rename, trash and restore
paths so the native File Provider change-feed refreshes the affected
folder.
Android DocumentsProvider:
- Fall back to the root (user) bucket when the destination folder's
  metadata has no bucket — the API only populates bucket on the root
  folder, so copies into subfolders failed with "Parent folder has no
  bucket". The resolved bucket is cached per process.
- Serve queryDocument from a new in-memory DocumentRowCache populated
  by folder listings and invalidated on rename/move/delete and child
  refresh; cache-miss fetches now run off the binder thread. Callers
  that query from their main thread (e.g. Google PDF viewer) no longer
  crash with NetworkOnMainThreadException, since the caller's
  StrictMode policy propagates across binder. Adds DocumentRowCacheTest.

iOS:
- Embed InternxtFileProvider.appex into the app bundle (the Embed
  Foundation Extensions phase was empty, so the extension never shipped
  and the provider could not appear in Files.app).
- Align the extension MARKETING_VERSION with the host app (1.10.0);
  a mismatched version prevents the extension from loading.
- CocoaPods integration for the In
  (Podfile.lock checksum update).
- Document intentionally empty blocks (CallAwait closeQuietly catch,
  FileProviderEnumerator invalidate)
- Mark unused NSFileProviderReplicatedExtension parameters with `_` in
  FileProviderExtension
- Extract duplicated string literals into constants in Android
  DocumentRowBuilder, DocumentRowCache, MimeTypes and InternxtApiClient tests
- Mock BaseLogger in auth.syncNativeCredentials.spec so the suite runs again
…p file extensions

- Implement isChildDocument (ancestry walk via cached rows, API fallback) so
  clients holding a folder grant (Use this folder, Material Files, galleries)
  no longer fail with "Document X is not a descendant of Y" / "file corrupted".
- Build file display names as plainName + type so copies and web-uploaded
  files show their extension.
- Rename keeps the file type immutable (as iOS/web do); renaming to the same
  base name is a no-op instead of a server 409, and the API call sends
  plainName only.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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.

3 participants