Skip to content

Firebase Sync Feature Added#2411

Open
rappc87 wants to merge 12 commits intorecloudstream:masterfrom
rappc87:master
Open

Firebase Sync Feature Added#2411
rappc87 wants to merge 12 commits intorecloudstream:masterfrom
rappc87:master

Conversation

@rappc87
Copy link

@rappc87 rappc87 commented Jan 10, 2026

### How to Set Up Firebase Sync ###

Step 1: Create a Firebase Project
Go to the Firebase Console.
Click "Create a project".
Enter a name (e.g., CloudStream Sync).
Disable Google Analytics (optional).
Click "Create project".

Step 2: Enable Firestore Database
In the sidebar, go to Build > Firestore Database.
Click "Create database".
Choose a Location (e.g., eur3 or us-central).
Select "Start in test mode".
Click "Create".

Step 3: Get Your Credentials (Android Method)
Click the Gear icon (Settings) > Project settings.
Scroll to "Your apps" and click the Android icon (little robot).
Package name: Enter com.lagradost.cloudstream3.
Click Register app.
Click Download google-services.json.
Open the downloaded JSON file with a text editor (Notepad, VS Code) and find these lines:
"project_id" -> Your Project ID
"mobilesdk_app_id" -> Your App ID
"current_key" -> Your API Key

Step 4: Configure CloudStream
Open CloudStream.
Go to Settings > Accounts & Security > Firebase Sync.
Enter the 3 values you found in the JSON file.
Click Connect & Sync.

@phisher98
Copy link
Contributor

phisher98 commented Jan 11, 2026

At the moment, when the selection is changed on one device, it automatically updates on the other device as well. While the sync speed has improved, I don’t think this behavior is desirable.

Could we exclude the selected extension from syncing? Personally, I’m not a fan of this approach, as users who share credentials for sync may not expect selections on one device to affect another.

Copy link
Collaborator

@fire-light42 fire-light42 left a comment

Choose a reason for hiding this comment

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

This is a very good feature that has been both requested a lot, and tried both in extensions and other pull requests.

However you still have some core architectural problems that I want to be fixed before I can do a full indepth review.
These are "Security" and what you call "Zombie keys".

  1. Security, while CloudStream has a few security problems, storing the entire backup on the cloud without any sort of password/auth is really bad.
    The very first thing they write in the "Firebase" setup is to never ever use "test mode" in production. So please fix your firebase rules, as otherwise anyone with the information will be able to download and modify your entire backup, which in this case even allows for Zero-click RCE with loading custom plugins/extensions. Storing plugins in a "sync" feature is a bit weird and dangerous. If you really want plugins to be shared across devices, then only sync their corresponding urls and prompt the user for a popup detailing all plugins and repositories before installing any of them.

Moreover, I would also want some sort of multi-user database here, as it is quite frustrating for each user to create a separate "Firestore Database". For example
a few users should be able to share the same database, like family members. This is not strictly needed, but I expect something like this will come for free when you make the firebase rules more strict, like https://firebase.google.com/docs/rules/basics#content-owner_only_access.

  1. Zombie keys. I have already written a bit about it here if you want some extra background: #1641 (comment)
    However, the core issue is that keys will have conflicts and may be deleted. As you noticed, if we remove a key how should that be updated to other devices because the current system is a merge only. Your solution right now only works on resume watching and plugins by adding a RESULT_RESUME_WATCHING_DELETED, and addedDate+isDeleted. This does not scale well, and requires all keys to either embed additional metadata, or store some extra data making it ugly and harder to maintain. Unfortunately, everything must have this to prevent very weird sync bugs, so you can't get away with only adding it on the essential data types. A more generic system that works on all keys reliably is needed to avoid this coupling of sync and the rest of the codebase.

This is only the most critical aspects of this pull request, and expect more comments about:

  1. Bugs/crashes
  2. Performance
  3. Localization
  4. Code quality
  5. Dependencies
  6. UI+UX and navigation

After you have finished these two major hurdles.

@rappc87 rappc87 requested a review from fire-light42 February 8, 2026 15:05
@rappc87
Copy link
Author

rappc87 commented Feb 8, 2026

This is a very good feature that has been both requested a lot, and tried both in extensions and other pull requests.

However you still have some core architectural problems that I want to be fixed before I can do a full indepth review. These are "Security" and what you call "Zombie keys".

  1. Security, while CloudStream has a few security problems, storing the entire backup on the cloud without any sort of password/auth is really bad.
    The very first thing they write in the "Firebase" setup is to never ever use "test mode" in production. So please fix your firebase rules, as otherwise anyone with the information will be able to download and modify your entire backup, which in this case even allows for Zero-click RCE with loading custom plugins/extensions. Storing plugins in a "sync" feature is a bit weird and dangerous. If you really want plugins to be shared across devices, then only sync their corresponding urls and prompt the user for a popup detailing all plugins and repositories before installing any of them.

Moreover, I would also want some sort of multi-user database here, as it is quite frustrating for each user to create a separate "Firestore Database". For example a few users should be able to share the same database, like family members. This is not strictly needed, but I expect something like this will come for free when you make the firebase rules more strict, like https://firebase.google.com/docs/rules/basics#content-owner_only_access.

  1. Zombie keys. I have already written a bit about it here if you want some extra background: #1641 (comment)
    However, the core issue is that keys will have conflicts and may be deleted. As you noticed, if we remove a key how should that be updated to other devices because the current system is a merge only. Your solution right now only works on resume watching and plugins by adding a RESULT_RESUME_WATCHING_DELETED, and addedDate+isDeleted. This does not scale well, and requires all keys to either embed additional metadata, or store some extra data making it ugly and harder to maintain. Unfortunately, everything must have this to prevent very weird sync bugs, so you can't get away with only adding it on the essential data types. A more generic system that works on all keys reliably is needed to avoid this coupling of sync and the rest of the codebase.

This is only the most critical aspects of this pull request, and expect more comments about:

  1. Bugs/crashes
  2. Performance
  3. Localization
  4. Code quality
  5. Dependencies
  6. UI+UX and navigation

After you have finished these two major hurdles.

Can you test it now?

@rappc87
Copy link
Author

rappc87 commented Feb 9, 2026

image

RULES :

rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; match /{document=**} { allow read, write: if request.auth != null && request.auth.uid == userId; } } } }

Copy link
Collaborator

@fire-light42 fire-light42 left a comment

Choose a reason for hiding this comment

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

This is a cursory review, but overall very nice changes to the key system. However you still have some things that needs to be fixed.

  1. All strings that the user will see needs to be translatable, see strings.xml
  2. Do not create UI manually, use a RecyclerView and the style classes we use for all other UI.
  3. Please do not touch any files that are not part of this pull request, or refactor anything. It will create merge issues and makes it harder to review. If you want something refactored make a small pull request with that change.
  4. Check out the AuthAPI we have, it provides a uniform and minimal API for auth.
  5. Please explain each and every dependency, right now you have pulled in libs that looks superfluous. Moreover, you have changed things that look weird, like the minSdk and androidGradlePlugin.
  6. Please do not crash the app with requestContext() or similar.

}

val text = Html.fromHtml(textBuilder.toString())
val text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do not touch files that do not concern this pull request.

?: return
}

override fun setPreferredAudioTrack(trackLanguage: String?, id: String?, formatIndex: Int?) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do not touch files that do not concern this pull request.

binding.syncCopyLogsBtn.setOnClickListener {
val logs = FirestoreSyncManager.getLogs()
if (logs.isBlank()) {
showToast("No logs available yet.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

All strings that the user can see should be translated with strings.xml

}

private fun setupDatabaseConfigInputs(binding: FragmentSyncSettingsBinding) {
val context = requireContext()
Copy link
Collaborator

Choose a reason for hiding this comment

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

All calls to requireContext may crash the application, and as such should be accessed with context ?: return

val context = requireContext()
binding.apply {
// Fix: Use getKey<String> to ensure we get the clean string value (handling JSON quotes if any)
syncApiKey.setText(context.getKey<String>(FirestoreSyncManager.FIREBASE_API_KEY) ?: "")
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have a login system called AuthAPI that should be used to both store and show login UI. Please check out out, as it unifies the login, and allows for multiple accounts.

android.javaCompile.suppressSourceTargetDeprecationWarning=true

# Disable path check for non-ASCII characters (e.g. 'Masaüstü')
android.overridePathCheck=true
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why?

prefs.edit {
remove(path)
}
fun Context.containsKey(folder: String, path: String): Boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I understand why you might want to refactor the datastore out of the object, however this is not allowed as it breaks plugins that uses the DataStore and is built on a different version. We do not want to break all plugins for no good reason.

}
}

private fun setupGranularToggles(binding: FragmentSyncSettingsBinding) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Recycleview.

setLocalTimestamp(context, key, t)

// Check for Continue Watching updates and trigger UI refresh
if (key.contains("result_resume_watching")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be invoked at max once per applyRemoteData, otherwise it will cause a lot of unnecessary lag.

internalName,
pluginUrl,
true,
false, // Mark as local so it updates PLUGINS_KEY_LOCAL immediately
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should not be used like this. isOnline is used for plugins installed locally, vs plugins that can be downloaded from a repo.

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