Skip to content

Official documentation

Help Center

Everything about WatchVerse: how to install it, use each feature, solve problems and understand the architecture.

Updated on November 3, 2026 · watchverse.fun/documentation

WatchVerse is an Android app that brings online radio, live TV, news, football, movies, series, anime, manga and games together in one place. Catalog access is unlocked by a Google (Gmail) account and an active 30-day pass, sold for US$ 1.

This Help Center is the official documentation. It is published at https://watchverse.fun/documentation and is also opened by the Help button inside the app, under Profile → Help.

Where to begin

For developers and administrators

What WatchVerse does not do

  • It does not host movies, series, channels or manga. The app organizes catalogs and opens third-party players/readers.
  • It does not sell a recurring subscription: access is a one-time 30-day purchase.
  • It does not work on iPhone in this version. The target is Android 10 or later.
  • It does not guarantee the availability of third-party content. Catalog, channels and chapters can change without notice.

Documented version

This documentation describes app 4.4.13 (versionCode 19) and the site published at watchverse.fun. When a feature exists in the code but is not used in the current version, that is stated explicitly in the relevant chapter.

User

Getting started

Requirements

ItemValue
SystemAndroid 10 (API 29) or later
ConnectionInternet required for sign-in, catalog, channels, news and payment
AccountVerified Google/Gmail account
StorageThe current APK is about 120 MB
OrientationPortrait and landscape (includes tablets, Samsung DeX and compatible TVs)

WatchVerse is not available for iPhone in this version. On devices without Google Play services, sign-in may not work.

1. Download and install

  1. Open https://watchverse.fun and tap Download APK for Android.
  2. Check the SHA-256 shown on the download page if you want to validate the file. The site also publishes the .sha256 file.
  3. When you open the APK, Android may ask for permission to install apps from an unknown source. Allow it only for the browser or file manager used for the download.
  4. Finish the installation and open WatchVerse.

The app does not request camera, storage, media or notification permissions. Those permissions were removed from the manifest.

2. Sign in with Google

On first launch, the app shows the sign-in screen. Tap Continue with Google and choose a Gmail account.

  • Accounts that are not Gmail are refused.
  • Sign-in uses Google Sign-In and Firebase Authentication.
  • The account is linked to a single device. If the same account is used on another device, access is blocked with an unauthorized device message.

3. Confirm the 8-digit code

In some cases the Worker requires an additional verification by email. The app opens the code screen.

RuleValue
Format8 digits
Validity5 minutes
Attempts3 per code
ResendFrom 60 seconds
LimitUp to 5 codes per hour per account

Enter the code received at the account email. If it expires, request another one. Also check the spam folder.

After confirmation, the app stores a session token in Android secure storage (SecureStore). That token is required by the access and paid-content routes.

4. Activate access

If the account does not have active access yet, the app opens the Activate your access screen.

  • Price: US$ 1 for 30 days.
  • One-time payment by credit or debit card, processed by Stripe.
  • After paying, the app checks the status automatically for up to 30 seconds (15 attempts every 2 seconds).
  • If the payment was already made, use Already paid, verify.
  • The screen also allows you to Sign out.

When access is confirmed, the app unlocks the main navigation. The status, expiration date and activation key (when present) appear under Profile.

5. Renew

Access does not renew automatically. When it expires, the app returns to the activation screen and the access card in Profile shows Expired.

  1. Open Profile.
  2. On the access card, tap Renew (or Pay 1 USD).
  3. Complete the payment and use Already paid, verify if needed.

First run: what happens

  1. The native splash screen is shown.
  2. The app runs integrity checks in production (development environment, emulator, root and signature).
  3. A short brand animation is shown.
  4. Firebase, player providers and the catalog are initialized.
  5. If initialization fails, an error screen appears with an option to try again.

In production, the app blocks emulators, rooted devices and builds signed with a different key than the official one. Screen capture is also prevented while the app is in use.

Updates

The current version does not run OTA updates inside the app. To update, download the latest APK from the official site and install it over the existing version. The signature must be the same; for that reason, do not install APKs from other sources.

If something goes wrong

  • Sign-in refused: confirm that the account is Gmail and that Google Play services are available.
  • Code does not arrive: wait 60 seconds, check spam and try again within the limit of 5 per hour.
  • Payment does not unlock: wait a few moments and tap Already paid, verify. If it persists, use the site contact.
  • Unauthorized device: the account is already linked to another device. Support must authorize the change.
  • Connection error: see Troubleshooting.

↑ Back to top

User

Catalog and player

Where the data comes from

Movies, series, anime and trailers use TMDb (The Movie Database), accessed through the WatchVerse Worker. The app sends the chosen language (pt-BR or en-US) and the corresponding region. Titles, synopses and images may vary by language.

The app does not host videos. It builds the title page, lets you choose an available provider and opens the external player inside a WebView.

Movies, series and anime

The three screens use a grid with infinite scroll:

  • Movies — popular and discovered by filters.
  • Series — popular and discovered by filters.
  • Anime — animated series with Japanese as the original language.

Each screen has:

  • initial loading with skeletons;
  • error message with Try again;
  • loading of additional pages when reaching the end;
  • responsive layout for phone, tablet and DeX.

Details screen

When you tap a title, the details screen shows:

  • poster, backdrop, title, year, rating and synopsis;
  • genres and cast;
  • YouTube trailer, when available;
  • seasons and episodes (series and anime);
  • favorite button;
  • list of available providers/players;
  • button to continue from where you stopped, when there is saved progress.

Trailers

The trailer opens in a modal with the YouTube player. If the video does not exist or cannot be loaded, the app shows a message and keeps the rest of the page working.

Providers

The provider list comes from the Firebase Realtime Database (streaming_providers) and may include personal links (personal_links). Each provider supplies a name, type, icon, priority and a URL template.

The app:

  1. selects the providers compatible with the title type;
  2. builds the embed URL with the TMDb ID, season and episode;
  3. accepts only HTTPS URLs;
  4. opens the internal player with a restricted list of allowed hosts.

If no provider loads, the screen shows a warning and offers to try again. Provider availability is external and can change without notice.

Player

The internal player uses react-native-webview and applies these rules:

RuleBehavior
ProtocolHTTPS only
HostsProvider allowlist + Google domains required by the embed
Google DriveFile links are converted to /preview mode
Playerflix / 2EmbedOnly recognized embed paths
OrientationImmersive landscape by default; button to switch
ScreenKeeps the device awake during playback
Screen captureBlocked in production

The X button closes the player. The rotation button switches between landscape and portrait.

Progress and "Continue watching"

Progress is saved:

  • every 15 seconds;
  • when the app goes to the background;
  • when the player is closed;
  • when the page sends the actual video position.

The app stores the position in seconds, the duration and the percentage. On reopening, it resumes the saved position when it is more than 20 seconds ahead of the start.

Progress stays on the device and, when signed in, is also synced to the account. That is what feeds the Continue watching rail on Home.

Real limitations

  • The TMDb catalog may be incomplete or outdated compared to other sources.
  • Some titles have no trailer, cast or available provider.
  • A provider may go offline, require its own login or block the region. WatchVerse does not control those services.
  • The player depends on the permissions of the embedded site. If the provider blocks embedding, the video will not open even with the correct link.
  • The app does not download movies or series for offline viewing.

Favorites and account

Movie and series favorites are stored per account. Without sign-in, the app uses local storage. When you sign in on another device with the same account, synced favorites appear in the Favorites tab.

Playback privacy

WatchVerse does not receive the video content: the WebView loads the provider's site. The provider may collect data under its own policies. Progress saved by WatchVerse contains only ID, title, type, position and item metadata.

↑ Back to top

User

Live TV and radio

Live TV

Live TV uses public channel lists (iptv-org) and fallback lists maintained by the app. The screen allows you to:

  • choose the country in the horizontal tabs;
  • search by name, category or group;
  • see the channel health indicator (online, slow, offline or checking);
  • favorite channels;
  • open the channel in the internal player.

Available countries

Brazil, United States, Spain, Argentina, Mexico, Colombia, Chile, Peru, Portugal, Italy, France, Germany, United Kingdom, Canada, Japan, South Korea and Uruguay. The All option groups the countries together.

How channels are loaded

  1. The app downloads the country's M3U list.
  2. It applies the public iptv-org blocklist.
  3. It removes HTTP channels; only HTTPS is accepted.
  4. It merges fallback channels for Brazil, Spain and the United States.
  5. It caches the result for 24 hours.
  6. It checks stream health and caches the result for 12 hours.
  7. Channels marked offline three times in a row stop appearing in the default list.

The health indicator is an estimate: it tests the stream's HTTP response, not the actual video quality.

TV player

The player uses expo-video with HLS support. It offers:

  • loading/buffering;
  • an error message when the channel does not open;
  • framing adjustment (fill the screen or stretch);
  • a button to open radio;
  • playback in portrait or landscape.

Some channels require specific Referer/User-Agent headers. The app forwards those headers when the list provides them.

Limitations

  • Channels may go offline, change URL or block the region.
  • Private/geo-blocked channels are not bypassed by the app.
  • Availability varies by country and provider.
  • WatchVerse does not host or redistribute the streams.
  • The Worker catalog mode (/api/iptv/*, with signed URLs) exists in the code but is optional and disabled by default. In the default configuration, the app uses the public lists.

Radio

Radio uses Radio Browser, queried through the Worker. The screen allows you to:

  • choose the country in the tabs (the same as TV, including All);
  • search by name, country, genre or language;
  • favorite stations;
  • open the radio player.

The Worker returns up to 100 stations per country, hiding broken stations and sorting by popularity. The list is normalized and duplicate URLs are removed.

Radio player

  • Background playback and lock-screen controls (title, artist and artwork when available).
  • Play/pause, reconnect and favorite buttons.
  • "Live" indicator.
  • Automatic reconnection with a warning when the stream drops.
  • Dedicated player with "now playing" bars.

Audio spectrum (limited)

The player can display an audio spectrum, but the feature depends on Android's audio recording permission. The current APK does not declare RECORD_AUDIO, so the spectrum tends to appear as unavailable or require a permission the system will not grant. Radio playback continues to work normally.

Limitations

  • Stations may be offline or change URL.
  • Order and availability come from Radio Browser and may vary.
  • HTTP streams are refused by the app.
  • There is no station download for offline listening.

↑ Back to top

User

News and football

News

The News section gathers headlines by country, with a summary and a link to the original source.

Countries

Brazil, United States, United Kingdom, France, Germany, Spain, Italy, Portugal, Japan and China.

How it works

  1. The Worker collects headlines and content from public sources.
  2. The text is summarized automatically and limited to 3,000 characters.
  3. The summary, image and metadata are stored in the Worker's D1 database.
  4. The app requests 20 articles per page and shows the most recent ones.
  5. When an article is opened, the app loads the summary and available media.

The Worker updates one country every 20 minutes, in rotation. Articles are kept for the country's local day and cleaned up afterward.

What appears in the app

  • title, source, country and time;
  • a "recent" badge for items from the last 6 hours;
  • cover image, when available;
  • summary in Portuguese or in the source language, depending on the country;
  • up to 4 media items (images or video) when the source and license allow it;
  • a button to open the original news item.

Licenses and credits

The Worker only accepts media with a compatible license (for example, Creative Commons CC BY or CC BY-SA) and keeps the license name and link. The article in the app displays the source and credit. If the license is not recognized, the media is discarded.

Important notices

  • The summary is generated automatically and may contain inaccuracies. Always check the original source.
  • WatchVerse is not the author of the news and does not control third-party content.
  • External links open in the device browser.
  • There is no offline reading or permanent storage of articles.

Football

The Football section shows data from eight competitions, obtained from football-data.org v4 through the Worker.

Country/regionCompetition
BrazilCampeonato Brasileiro Série A
ItalySerie A
GermanyBundesliga
SpainLa Liga (Primera Division)
EnglandPremier League
FranceLigue 1
PortugalPrimeira Liga
NetherlandsEredivisie

Tabs

  • Standings — position, team, points, matches, wins, draws, losses, goals for/against and goal difference.
  • Results — finished matches, with score and round.
  • Upcoming matches — future matches, with date, time and venue when available.

Updates and cache

  • The Worker caches the data for about 5 minutes.
  • The screen allows manual refresh.
  • When the refresh fails, the app may show older data with a cache/instability warning.
  • Matches in progress receive the LIVE badge when the source reports that state.

Limitations

  • Football is informational only: there is no match streaming in this section.
  • The source may delay scores or not cover smaller competitions.
  • Team crests and names depend on the API response.
  • Standings may appear incomplete while the source is updating.

↑ Back to top

User

Manga and games

Manga

Manga comes from MangaDex. The app queries the API directly; on the site, the same catalog uses a Netlify proxy to avoid CORS.

Catalog

  • grid with 3 columns and 24 titles per page;
  • infinite scroll;
  • search with typing delay;
  • filter by translation language;
  • only titles rated safe are requested from MangaDex.

Languages available in the filter: all, Portuguese (BR), English, Spanish, French, Italian, German, Russian, Indonesian, Polish, Vietnamese, Turkish, Arabic, Chinese, Japanese and Korean.

Manga details

The screen shows cover, title, year, status, authors, description and the chapter list. You can:

  • filter chapters by language;
  • continue from the saved chapter;
  • open any chapter in the reader.

By default, fetched chapters include Portuguese (BR) and English, in addition to the language selected in the catalog. This prevents a manga from appearing to have "no chapters" when the filter is set to another language.

Reader

  • continuous vertical reading;
  • page counter;
  • preloading of the next two pages;
  • navigation to the previous/next chapter;
  • screen kept awake during reading;
  • immersive mode (status bar hidden).

Progress is saved on the device per manga, with the chapter ID, label, language and date. The key used is wv_manga_progress_v1, the same as the site reader, which makes future export/import easier.

Limitations

  • Chapter availability depends on MangaDex and scanlation groups.
  • There is no download for offline reading.
  • Manga progress is local; it does not sync between devices.
  • Adult content is not requested by the app (contentRating=safe).

Games

The games catalog uses RAWG, with optional enrichment of descriptions and trailers from Steam, all through the Worker.

Catalog

  • sorting by popular, recent or upcoming;
  • genre filter;
  • search by name;
  • 30 games per page, in a 2-column grid;
  • local cache of 10 minutes for lists and 6 hours for details.

Game details

The page may show:

  • cover, backdrop, year and rating;
  • Metacritic score, average playtime and platforms;
  • description (RAWG or Steam, depending on availability);
  • genres, developers, publishers and official website;
  • screenshots in a zoomable viewer;
  • Steam trailers;
  • a button to hear the description by voice (device TTS).

The Media screen gathers screenshots and trailers in a dedicated grid.

Limitations

  • WatchVerse does not sell, download or run games. It is an informational catalog.
  • Trailers only appear when the Worker can enrich the game with Steam data.
  • Descriptions and images may vary by language and source.
  • Some games have no screenshots, trailers or description.
  • TTS depends on the voices installed on Android; if there is no voice for the language, the app warns the user.

↑ Back to top

User

Account, access and privacy

Account

WatchVerse uses Google sign-in only. The account must be a verified Gmail account. The profile shows the Google name, email and photo; the avatar can be changed to a ready-made option or an image from the gallery.

The account is linked to one device. The identifier used is the Android ID (SSAID). If the account is opened on another device, the Worker returns DEVICE_MISMATCH and the app blocks access until support authorizes the change.

License

ItemValue
PriceUS$ 1
Duration30 days
BillingOne-time card payment (Stripe)
RenewalManual; no automatic debit
Possible statusesACTIVE, EXPIRED, NONE, DEVICE_MISMATCH, OTP_REQUIRED

The access card under Profile shows:

  • current status and remaining days;
  • expiration date;
  • activation key, when the backend provides one;
  • renewal button;
  • refresh status button.

How the status is checked

The app calls GET /access/status with the Firebase token, the device ID and the session token. The Worker responds with the status, expiration and remaining days.

  • ACTIVE: unlocks the app.
  • EXPIRED or NONE: opens the payment screen.
  • OTP_REQUIRED: opens verification by code.
  • DEVICE_MISMATCH: blocks and instructs the user to contact support.
  • ERROR: shows a connection error with an option to try again or sign out.

Payment

The app creates a checkout session on the Worker and opens the Stripe page in a WebView. When the payment is completed, Stripe calls the Worker webhook, which stores the access in Firebase. The app checks the status for up to 30 seconds.

If the payment was made but access did not appear, use Already paid, verify. Do not pay twice without checking the status first.

Biometrics

Biometrics are optional and configured under Profile → Settings. They protect the already authenticated session; they do not replace Google sign-in.

  • Requires strong biometrics enrolled on Android.
  • The preference is stored in secure storage.
  • If authentication fails, the app offers to sign out.
  • On devices without compatible biometrics, the option does not work.

Signing out

Profile → Sign out ends the Firebase session, revokes the session token on the Worker when possible and clears the local token. Local favorites and progress remain on the device; synced data remains in the account.

Collected data

WatchVerse collects the minimum necessary to authenticate, unlock access and keep the service running. In general:

DataPurpose
Google name, email and photoProfile and account identification
Firebase UIDLink favorites, progress and license
Android ID (SSAID)Link the account to a device
Model, brand, Android version and app versionDiagnostics and device registration
IP address (hash) and approximate country/region/citySecurity, anti-fraud and statistics
License status and expirationUnlock access
Favorites and playback/reading progressSync the experience
Notification read stateUnread counter
Ad installation identifierMeasure impressions and clicks
Ad events (impression, click, close)Campaign reporting
Error diagnostics (Sentry)Fix failures

The app does not request camera, storage, media or notification permission. App backup is disabled (allowBackup=false).

Local storage

  • AsyncStorage: local favorites, progress, Home preferences, catalog cache, chosen avatar, read notifications and banner cache.
  • SecureStore: session token, biometric preference, license state and language.

None of this is accessible to other apps on a non-rooted device.

Ads

The banner is loaded from the Firebase Realtime Database and shown after 60 seconds of use for active accounts. The app sends impression, click and close events to the Worker, with an installation identifier, app version and approximate region data. The ad content is HTML provided by the backend; when a link is tapped, the app validates the URL and opens it in the browser.

App security

In production builds:

  • emulators, root and the development environment are blocked;
  • the APK signature is verified;
  • screen capture is prevented;
  • all traffic is HTTPS (cleartext blocked);
  • the app does not allow data backup;
  • player and ad WebViews apply a host allowlist and block insecure navigation.

Privacy and terms

  • Privacy Policy: https://watchverse.fun/privacidade.html
  • Terms of Use: https://watchverse.fun/termos.html
  • Contact: https://watchverse.fun/contato.html

To request data deletion or a device change, use the contact form with the account email. There is no automatic account deletion inside the app in this version.

↑ Back to top

User

Troubleshooting

Before contacting support

  1. Confirm that you are using the official APK from watchverse.fun.
  2. Check the internet connection.
  3. Close and reopen the app.
  4. In Profile, tap the button that refreshes the access status.
  5. If the problem is with a specific item, try another title/channel to separate an app failure from a provider failure.

Installation

"App not installed" or "invalid package"

  • Download the APK again; the file may have arrived incomplete.
  • Check the SHA-256 published on the site.
  • Uninstall a version from another source before installing the official one. Different signatures cannot coexist.
  • Check that there is enough free space (about 120 MB for the APK, plus installation space).

Android blocks the installation

  • Allow "install unknown apps" for the browser or file manager used.
  • On Android 14+, you may need to allow the package installer in settings.

App closes on open

  • In production, the app blocks emulators, root and builds with a different signature.
  • If the security screen appears, use a device without root and the official APK.
  • Restart the device and try again.

Sign-in

"Sign in with a Gmail account"

  • WatchVerse only accepts Gmail accounts. Corporate or other domain accounts are refused.

Google does not open or fails

  • Update Google Play services.
  • Check that a Google account is configured on the device.
  • Try another network.
  • If the error persists, sign out and try again.

The account works on another device, but not on this one

  • The account is already linked to another device. Use the site contact to request the change. The app does not allow unlinking by itself.

Verification code

The code does not arrive

  • Wait up to 60 seconds before requesting another one.
  • Check the spam/junk folder.
  • Confirm that the email is the same as the Google account.
  • There is a limit of 5 codes per hour.

"Code expired"

  • Request a new code. It is valid for 5 minutes.

"Too many attempts"

  • The code allows 3 attempts. Request another one and type carefully.
  • If the hourly limit was reached, wait before trying again.

Payment and access

I paid, but the app keeps asking for payment

  • Wait a few moments and tap Already paid, verify.
  • Confirm that the payment was approved on the card.
  • Check that the purchase email is the same as the signed-in account.
  • If the card was declined, try another payment method.

"Unauthorized device"

  • The account is linked to another device. Request authorization through the contact form.

"Could not verify your access"

  • It is a connection failure or Worker instability. Tap Try again.
  • If it persists, sign out and sign in again.
  • As a last resort, use the contact form.

Access expired earlier than expected

  • The Profile card shows the actual expiration date.
  • Renew normally; there is no automatic renewal.

Live TV

The channel does not open

  • Try another channel from the same country.
  • The channel may be offline or geo-blocked.
  • The health indicator may be outdated; refresh the list.
  • Channels that failed three times may disappear from the default list.

The list is empty

  • Change the country and come back.
  • Pull to refresh.
  • Check the connection. The list is cached for 24 hours.

The video freezes or keeps buffering

  • Try another network (Wi-Fi/mobile data).
  • Close other apps that use bandwidth.
  • Some channels are naturally unstable; this is not controlled by WatchVerse.

Radio

It does not play

  • Tap Reconnect.
  • Try another station.
  • Check that the stream is available in your country.
  • Confirm that the media volume is up.

It stops when the screen locks

  • The app supports background playback, but Android may end the process on devices with aggressive battery saving. Allow WatchVerse in the battery settings.

The audio spectrum does not appear

  • This feature depends on the audio recording permission, which the current APK does not declare. Radio works without the spectrum.

Movie and series player

Black screen or "invalid link"

  • The provider may have blocked embedding or gone offline.
  • Go back to the details and choose another provider.
  • Try another title to confirm that the problem is with the provider.

The video does not resume where I stopped

  • Progress is saved every 15 seconds and when leaving the player. If the app was force-closed, the last segment may be lost.
  • Resuming only happens when the difference is more than 20 seconds.

The player opens in landscape and does not return

  • Use the rotation button in the top right corner.
  • Leave the player and open it again.

News and football

Empty news

  • Change the country.
  • Pull to refresh.
  • The Worker updates one country every 20 minutes; there may be a delay.
  • If the source has no valid articles for the day, the list may be empty.

Outdated standings or scores

  • Use the refresh button.
  • The cache is about 5 minutes.
  • The source may be slow; the app shows older data with a warning.

Manga and games

Manga without chapters

  • Change the language filter to All.
  • Not every title has a Portuguese or English translation.
  • MangaDex may be unstable.

Pages do not load

  • Try another network.
  • The reader preloads only the next pages; scroll slowly.
  • If the chapter is broken at the source, choose another one.

Game without trailer or screenshots

  • Steam enrichment is optional and depends on the game.
  • Not every title has available media.

Notifications

I do not receive notifications with the app closed

  • Expected behavior in this version: Android does not register a push token. Notifications are in-app and appear when the app queries Firebase.

The bell does not appear

  • It only appears for accounts with active access and an unlocked session.
  • If there are no unread messages, the bell may stay hidden.

Help and site

The Help Center does not open in the app

  • Use Try again.
  • Use Open in browser.
  • Confirm that https://watchverse.fun/documentation opens in the browser.
  • The app requires HTTPS; networks that block the domain prevent it from opening.

Performance, data and battery

  • Clear the app cache in Android settings if storage grows.
  • Use Wi-Fi for the catalog, trailers and channels.
  • Background playback consumes battery; close the player when not using it.
  • On devices with little memory, Android may end the app when switching tasks.

How to contact support

Use https://watchverse.fun/contato.html and provide:

  • account email (without sending a password or code);
  • device model and Android version;
  • app version (Profile or splash screen);
  • what you were trying to do;
  • the exact error message;
  • a screenshot, if possible.

Never send a verification code, token, password or card data.

↑ Back to top

Developer

Architecture

Overview

WatchVerse is an Expo / React Native app for Android, with a Cloudflare Worker as the backend. The app does not host media: it consumes public catalogs and opens third-party players.

LayerTechnology
AppExpo SDK 57, React Native 0.86.3, React 19.2.3, TypeScript
NavigationReact Navigation 7 (stack + bottom tabs)
Global stateReact Context (AuthContext, HomeBootstrapContext, RadioPlaybackContext)
Local persistenceAsyncStorage and SecureStore
AuthenticationFirebase Authentication + Google Sign-In
Realtime databaseFirebase Realtime Database
BackendCloudflare Worker (TypeScript) + D1 + R2 + Workers AI
PaymentStripe Checkout + Worker webhook
Crash/errorSentry React Native
BuildEAS Build / Gradle (assembleRelease)

Folder structure

wv/
├── App.tsx                  # root, security gate, authenticated navigation
├── index.ts                 # Expo entry point
├── src/
│   ├── components/          # reusable UI (banner, notifications, biometrics…)
│   ├── config/              # Config, NewsConfig, FootballConfig, GamesConfig, MangaLanguageConfig
│   ├── context/             # AuthContext, HomeBootstrapContext, RadioPlaybackContext
│   ├── data/                # curated collections (Sci-Fi Top 100, ranked collections)
│   ├── hooks/               # useRadioPlayer, useNetworkConnectivity, useRefresh
│   ├── localization/        # ptBR, enUS, full dictionaries, localeStore
│   ├── navigation/          # MainTabNavigator, navigationRef
│   ├── screens/             # 34 screens
│   ├── security/            # AppSecurityService, root/emulator/signature
│   ├── services/            # 34 domain and infrastructure services
│   ├── theme/               # Theme (colors, spacing)
│   ├── types/               # API, IPTV, radio, navigation, playback types
│   └── utils/               # safeUrl, mangaLabels and utilities
├── android/                 # generated native project (manifest, gradle)
├── docs/                    # this documentation (single source)
├── quality-gate/            # quality and security checks
├── scripts/                 # check-translations, generate-icons, build-docs
└── __tests__/               # Jest tests

Startup

The actual opening flow is:

  1. index.ts registers the root component.
  2. App runs the security checks (AppSecurityService) with a 15 s timeout.
  3. While checking, it shows the splash/indicator.
  4. If the device is blocked (root, emulator, mismatched signature in production), it opens SecurityBlockedScreen.
  5. If allowed, it mounts AuthProvider, HomeBootstrapProvider and RadioPlaybackProvider.
  6. RootNavigator decides between Login, VerifyCodeScreen, CheckoutScreen or the authenticated app.
  7. BiometricGate protects the session when biometrics are enabled.
  8. AuthenticatedNavigator mounts the main stack and the tab bar.

There is a 20 s startup watchdog and a 15 s timeout for authentication loading, to avoid a stuck screen on poor networks.

Navigation

The root stack (App.tsx) contains:

  • Main — tab navigator;
  • Details, Player, LivePlayer;
  • Checkout;
  • Help — Help Center in a WebView;
  • MangaDetails, MangaReader;
  • GameDetails, GameMedia;
  • HomeCollection — "See all" lists (Sci-Fi Top 100 etc.);
  • PerformanceTests and QualityGate — development only.

The five tabs (MainTabNavigator.tsx) have their own stacks:

TabStack
HomeHomeFeed, Manga, Games, LiveTv, Radio, RadioPlayer, News, NewsArticle, Football
ContentContentHome, Movies, Series, Anime, Manga, Games
LiveLiveHome, LiveTv, Radio, RadioPlayer, News, NewsArticle, Football
+MoreHome, Search, Favorites
Profilesingle screen ProfileScreen

Duplicating screens across stacks is intentional: each tab keeps its own history. navigationRef allows navigation outside components (for example, when a notification is tapped).

Contexts

AuthContext

Centralizes authentication and access state:

  • Firebase user and initialization state;
  • access status (ACTIVE, EXPIRED, NONE, DEVICE_MISMATCH, OTP_REQUIRED, UNKNOWN, ERROR);
  • refreshAccess() and logout();
  • biometric unlock (sessionUnlocked);
  • device registration and data synchronization.

HomeBootstrapContext

Loads Home data in parallel (trending, popular, anime, etc.) to reduce waiting and avoid repeated requests. It exposes loading and error state per block.

RadioPlaybackContext

Keeps the radio player alive across screens:

  • current station and playback status;
  • background playback and lock-screen controls;
  • automatic reconnection;
  • audio spectrum (when allowed).

Services

Services are organized by domain. The main ones:

ServiceResponsibility
AuthService / GoogleAuthServiceGoogle sign-in, sign-out, profile
AccessServiceLicense status, OTP, checkout, session
SecureApiClientAuthenticated requests to the Worker
TmdbServiceMovie, series and anime catalog
ProviderServicePlayer providers and embed assembly
IptvService / WorkerIptvService / LiveTvHealthServiceChannels, cache, health and Worker mode
RadioServiceStations through the Worker
NewsServiceNews and articles
FootballServiceCompetitions, tables and matches
MangaDexService / MangaProgressServiceCatalog, chapters and progress
GamesServiceRAWG catalog and details
FavoritesServiceLocal and synced favorites
PlaybackProgressServicePlayback progress
NotificationsServiceIn-app messages and read state
BannerHtmlService / AdTrackingServiceBanner and ad events
DeviceTrackingServiceDevice registration and update
NetworkServiceConnectivity and offline handling
PersistentCacheTTL cache in AsyncStorage
StorageServiceKeys and secure read/write

Storage

AsyncStorage

Stores non-sensitive data and caches:

  • local favorites;
  • playback and manga progress;
  • Home preferences;
  • catalog cache;
  • chosen avatar;
  • notification read state;
  • banner cache.

SecureStore

Stores sensitive data:

  • Worker session token;
  • biometric preference;
  • cached license state;
  • language.

The app uses allowBackup=false, so this data is not included in Android backup.

Localization

  • src/localization/ptBR.ts and enUS.ts are the base dictionaries.
  • ptBR.full.ts and enUS.full.ts extend the dictionaries with additional keys.
  • LanguageContext exposes useT() and the current language.
  • localeStore persists the choice and reports the locale to TMDb.
  • npm run check:translations validates key parity.

Theme and layout

Theme centralizes colors, spacing and typography. The app is dark-only, with red #E50914 as the accent. Screens use SafeAreaView, responsive grids and tablet/DeX adjustments. The default orientation is free (orientation: default), with players in immersive landscape.

Structural security

  • AppSecurityService blocks root, emulator, debug and mismatched signatures in production.
  • expo-screen-capture prevents screen capture.
  • network_security_config blocks cleartext.
  • WebViews validate HTTPS and allowed hosts.
  • The Worker validates the Firebase ID token, verified Gmail email, active entitlement and deviceId.

Details in Security model.

↑ Back to top

Developer

Environment and build

Requirements

ToolVersion/note
Node.js20 LTS or later (recommended)
npm10 or later
JDK17 (required by the current Android Gradle Plugin)
Android SDKAPI 36, compatible build-tools
Gradlethrough the project wrapper
Expo/EAS accountrequired for cloud builds
Wranglerto deploy the Worker

The project uses expo@^57, react-native@0.86.3, minSdk 29 and targetSdk 36.

Installation

cd wv
npm ci

postinstall runs patch-package, applying the patches in patches/. Do not remove this step.

Environment variables

The app reads EXPO_PUBLIC_* variables at build time. Never commit real values. The expected keys are:

VariableUse
EXPO_PUBLIC_FIREBASE_API_KEYFirebase
EXPO_PUBLIC_FIREBASE_AUTH_DOMAINFirebase Auth
EXPO_PUBLIC_FIREBASE_DATABASE_URLRealtime Database
EXPO_PUBLIC_FIREBASE_PROJECT_IDFirebase
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKETStorage (avatars)
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_IDFirebase
EXPO_PUBLIC_FIREBASE_APP_IDFirebase
EXPO_PUBLIC_FIREBASE_MEASUREMENT_IDAnalytics (optional)
EXPO_PUBLIC_WORKER_ORIGINWorker origin (default: https://ad-server-worker.gabrieldossantoselizeu3.workers.dev)
EXPO_PUBLIC_ANDROID_SIGNATURE_SHA256SHA-256 of the certificate expected in production
EXPO_PUBLIC_USE_WORKER_IPTV1 enables the IPTV catalog through the Worker
EXPO_PUBLIC_ENABLE_LICENSE_GATETurns the license gate on/off
EXPO_PUBLIC_PURCHASE_URLAlternative purchase URL (when used)
EXPO_PUBLIC_IMAGE_BASE_URLImage base URL (when configured)
EXPO_PUBLIC_QUALITY_GATE1 enables the quality gate screen in dev
EXPO_PUBLIC_QUALITY_GATE_STRESSquality gate stress mode
EXPO_PUBLIC_PERFORMANCE_TESTS1 enables performance tests in dev
EXPO_PUBLIC_PERF_STRESSperformance stress mode
EXPO_PUBLIC_QG_FORCE_OFFLINEforces an offline scenario in the quality gate

For local development, create .env from your secrets vault. The .env file must not be committed.

Native credentials

  • google-services.json — Android/Firebase.
  • GoogleService-Info.plist — iOS (the app is not distributed for iOS in this version).
  • @c_4_rl0s__WatchVerse.jks — release keystore. Do not commit the password.

Scripts

CommandWhat it does
npm startExpo dev server
npm run androidbuild and run on Android
npm run iosbuild and run on iOS
npm run webExpo web (not the production target)
npm testJest
npm run lintESLint
npm run check:translationsvalidates translation keys
npm run quality-checktranslations + quality gate
npm run docs:buildgenerates the published Help Center (pt + en)
npm run docs:checkchecks whether the generated pages are up to date
npm run build:android:apk:qualityquality check + EAS productionApk
npm run obfuscateoptional dist obfuscation

Building the APK

npm run build:android:apk:quality

The productionApk profile uses:

  • autoIncrement: true (increments versionCode on the EAS server);
  • :app:assembleRelease;
  • only arm64-v8a by default;
  • appVersionSource: remote.

Local Gradle

cd android
./gradlew :app:assembleRelease

The APK is written to android/app/build/outputs/apk/release/.

Signing

The current build uses v3 only (AGP 8.12). Since minSdk is 29, this is accepted by supported devices. If you need compatibility with older verifiers, post-sign with apksigner including v2:

apksigner sign --ks <keystore> --v1-signing-enabled false --v2-signing-enabled true --v3-signing-enabled true app-release.apk

The signature must match the SHA-256 configured in EXPO_PUBLIC_ANDROID_SIGNATURE_SHA256 (default 19d85449…6f7f). Builds with a different signature are blocked in production.

Tests

npm test
npm test -- newsService.test.ts

The project uses Jest with jest.setup.js and mocks in __mocks__/. There are tests for services, security, biometrics, player and components.

Known state in version 4.4.13: the main suite passes, but there are pre-existing failures in biometricGate.test.tsx, radioService.test.ts and iptvMemorySafety.test.ts. They are not caused by the documentation or the Help button.

Quality gate

quality-gate/quality-check.mjs and securityChecks.mjs verify:

  • translation parity;
  • Android security configuration;
  • declared permissions;
  • presence of secrets in committed files;
  • build targets and settings.

Run npm run quality-check before publishing.

Site publishing

The site lives in quase_dev_org/watchverse_webpage and is published by Netlify from the watchverse_website repository. The APK is stored with Git LFS in apk/.

cd quase_dev_org/watchverse_webpage
git add .
git commit -m "docs: ..."
git push origin main

The published documentation is generated from wv/docs (Portuguese) and wv/docs/en (English) by wv/scripts/build-docs.mjs. Do not edit the generated HTML by hand.

Release checklist

  1. Update version and versionCode in app.json.
  2. Run npm run quality-check and npm test.
  3. Build the APK and calculate the SHA-256.
  4. Publish the APK and the .sha256 file on the site.
  5. Generate the documentation (npm run docs:build).
  6. Validate the documentation pages on desktop and mobile.
  7. Test the Help button in the APK.
  8. Commit and publish the site.

↑ Back to top

Developer

Integrations

Integration map

IntegrationAccessAuthenticationUsed for
Firebase AuthenticationApp SDKGoogle ID tokenGmail sign-in
Firebase Realtime DatabaseApp SDKFirebase rulesProfile, favorites, progress, messages, banner, access
Firebase StorageApp SDKFirebase rulesUser avatar
Cloudflare WorkerHTTPSFirebase ID token + session + deviceCatalog and data
TMDbWorkerKey in the WorkerMovies, series, anime
MangaDexDirect app / site proxyNoneManga
RAWGWorkerKey in the WorkerGames catalog
Steam StoreWorkerNoneDescription and trailers
football-data.org v4WorkerToken in the WorkerFootball
freenewsapi.aiWorkerOptional keyNews
Workers AIWorkerBindingNews summaries
Radio BrowserWorkerNoneRadio
iptv-orgDirect appNoneTV lists
StripeWorkerSecret key + webhookPayment
SentryApp SDKDSNErrors and crashes
Expo PushWorkerExpo tokenFan-out (no registration in the current Android app)

No key is distributed in the APK, except the EXPO_PUBLIC_FIREBASE_* variables (which are public Firebase configuration) and the expected signature SHA-256. TMDb, RAWG, football, news, Stripe and Firebase Admin keys stay only in the Worker.

Firebase

Authentication

  • Provider: Google.
  • The app uses @react-native-google-signin/google-signin to obtain the ID token and Firebase to create the session.
  • The Worker validates the ID token against Google's public key and requires email_verified and a Gmail domain.

Realtime Database

Paths observed in the app code:

PathContent
users/{uid}Profile (name, avatar, dates)
users/{uid}/favoritesFavorite movies and series
users/{uid}/favorites_channelsFavorite TV channels
users/{uid}/playback_progressPlayback progress
messagesIn-app notifications
userMeta/{uid}/pushTokensPush tokens (not used in the current Android app)
streaming_providersPlayer providers
ads/bannerHtmlBanner HTML

The Worker also uses /access/{uid}, /entitlements/…, /payments/{sessionId}, /otp/{uid} and /devices/… for license, OTP and devices.

Storage

Used for avatars uploaded by the user. The photo is cropped before upload.

TMDb

  • Base: https://api.themoviedb.org/3, always through the Worker.
  • The app sends language (pt-BR/en-US) and region (BR/US).
  • The Worker caches at the CDN: 10 minutes for lists and 24 hours for details.
  • The Worker also warms the cache of the main lists in pt-BR/BR.

Available routes in Worker API.

MangaDex

  • Base: https://api.mangadex.org.
  • The app queries directly (it does not go through the Worker).
  • Filters: contentRating[]=safe, includes[]=cover_art, includes[]=author, translation language.
  • 24 titles per page.
  • The site uses a Netlify proxy (/api/mangadex/*) because MangaDex does not send CORS.

RAWG and Steam

  • RAWG base: https://api.rawg.io/api.
  • The Worker applies rate limiting and D1 caching.
  • Sort orders: popular (-added), recent (last 120 days) and upcoming (next 365 days).
  • 30 games per page (default), with a maximum configurable in the Worker.
  • When ENABLE_STEAM_ENRICHMENT=1, the Worker fetches description and trailers from the Steam store.
  • The detail language is pt or en, according to language/Accept-Language.

News

  • Source: https://freenewsapi.ai/v1/search.
  • The Worker queries 50 results per country, a 48-hour window, and keeps only the country's local day.
  • Summaries are generated with Workers AI and limited to 3,000 characters.
  • Media is only accepted with a recognized license (CC BY, CC BY-SA, CC0, public domain).
  • A cron every 20 minutes rotates the countries; cleanup of previous days runs every hour.
  • The app requests 20 articles per page.

Football

  • Base: https://api.football-data.org/v4.
  • The Worker stores standings, results and upcoming matches in D1 for about 5 minutes.
  • ?refresh=1 forces an update (subject to the provider's limit).
  • Competitions: br, it, de, es, gb, fr, pt, nl.

Radio

  • Source: https://de1.api.radio-browser.info/json/stations/search.
  • The Worker limits to 100 stations, hides broken stations and sorts by clicks.
  • The route requires paid access.

IPTV

Two modes:

  1. Default (direct app): iptv-org.github.io lists per country, HTTPS-only, public blocklist, 24 h cache and 12 h health check.
  2. Worker (optional): EXPO_PUBLIC_USE_WORKER_IPTV=1 enables /api/iptv/catalog, /access and /stream with signed URLs. Disabled by default.

Stripe

  • Hosted Checkout, price of US$ 1 (100 cents) and 30 days.
  • POST /checkout/session creates the session; the app opens the URL in a WebView.
  • POST /stripe/webhook validates the signature, checks amount/currency and stores the entitlement.
  • There is no recurring subscription.

Sentry

  • Initialized in the app to capture errors and crashes.
  • It does not send video content, passwords or tokens.
  • The DSN is build configuration and should not be treated as a critical secret, but it also does not need to be disclosed in user documentation.

Push

  • The Worker has fan-out through Expo Push.
  • In the current Android app, the app does not register a token (registerForPushNotificationsAsync returns null), and POST_NOTIFICATIONS is blocked.
  • Visible notifications are in-app, read from the Realtime Database.

Limits and failures

  • All third-party APIs may become unavailable; the app handles errors and cache when possible.
  • The Worker applies a timeout and response size limit.
  • Keys are never printed in logs; RAWG and football errors are logged without the URL containing credentials.

↑ Back to top

Reference

Worker API

Base

https://ad-server-worker.gabrieldossantoselizeu3.workers.dev

The app uses EXPO_PUBLIC_WORKER_ORIGIN to point to another origin. All calls use HTTPS.

Authentication

Public routes

No token required:

  • GET / and GET /health
  • GET /ad/next, GET /video/:id, GET /imagem/:id
  • POST /track
  • POST /auth/otp/send, POST /auth/otp/verify, POST /auth/session/logout
  • POST /checkout/session, POST /stripe/webhook, GET /access/status
  • GET /checkout/success, GET /checkout/cancel
  • POST /api/devices/register
  • POST /api/notifications/clicks

Paid content routes

Require, together:

  1. a valid Authorization: Bearer <Firebase ID token>;
  2. a verified Gmail email;
  3. an active entitlement (expiresAt in the future);
  4. x-device-id with 8 to 128 characters (A-Z, a-z, 0-9, _, -) matching the linked device.

Failures return:

StatuserrorMeaning
401unauthorizedMissing/invalid token
403verified_gmail_requiredAccount is not a verified Gmail
400device_requiredMissing/invalid x-device-id
403access_requiredNo active entitlement
403device_mismatchDevice differs from the linked one
503access_check_unavailableFailed to query Firebase

Administrative routes

Require x-admin-token (or x-dashboard-token on key routes):

  • POST /api/admin/access/revoke
  • POST /api/notifications/fanout/run
  • POST /api/activation-keys
  • GET /api/activation-keys/list
  • GET /api/activation-keys/export
  • POST /api/activation-keys/revoke

Ads and media

GET /ad/next

Returns the next ad.

QueryEffect
rn=1Returns HTML ready for WebView

JSON response:

{
  "success": true,
  "ad": {
    "id": "…",
    "type": "video",
    "clickUrl": "https://…",
    "src": "/video/…",
    "allowSkipAfter": 5,
    "muteByDefault": true
  }
}

No ad: { "success": true, "ad": null }.

GET /video/:id

Serves ads/{id}/video.mp4 from R2, with Range support (206).

GET /imagem/:id

Serves ads/{id}/image.{jpg,jpeg,png,webp} from R2, with a 1-day cache.

POST /track

Records ad events. Limit: 60 requests per minute per IP.

Accepted events: ad_impression, ad_click, ad_complete, banner_impression, banner_click, banner_close.

Common fields: event, adId, campaignId, placement, clickUrl, ts, installId, appVersion, watchedMs.

Authentication and session

POST /auth/otp/send

Sends an 8-digit code to the account email. Limit of 10 requests per minute per IP.

Rules: 5-minute validity, resend after 60 seconds, maximum of 5 codes per hour, 3 attempts per code.

POST /auth/otp/verify

Validates the code and returns a sessionToken of 64 hexadecimal characters. The token is stored in SecureStore.

POST /auth/session/logout

Invalidates the OTP and the session of the authenticated user.

Checkout and access

POST /checkout/session

Creates a Stripe session. Requires a Firebase ID token and a verified Gmail.

Response:

{ "ok": true, "sessionId": "cs_…", "url": "https://checkout.stripe.com/…" }

POST /stripe/webhook

Receives Stripe events. Validates the signature, accepts checkout.session.completed and checkout.session.async_payment_succeeded, checks amount (100 cents) and currency (usd) and stores the entitlement for 30 days. It is idempotent by session.id.

GET /access/status

Requires a Firebase ID token, verified Gmail and deviceId (query or header). Returns status, expiration, remaining days and activation key when present.

Status: ACTIVE, EXPIRED, NONE, DEVICE_MISMATCH, OTP_REQUIRED.

GET /checkout/success and GET /checkout/cancel

HTML return pages from Stripe.

Devices

POST /api/devices/register

Limit: 20 per minute per IP.

{
  "deviceId": "…",
  "deviceInfo": {
    "os": "android",
    "osVersion": "14",
    "brand": "samsung",
    "model": "SM-…",
    "appVersion": "4.4.13"
  }
}

Returns isNew, firstSeenAt, lastSeenAt and accessCount.

Notifications

POST /api/notifications/clicks

Limit: 60 per minute per IP.

Fields: source (push or in_app), action (route, tmdb, action_url, dismissed), notificationId, campaignId, actionTarget, route, routeParams, tmdbId, mediaType.

POST /api/notifications/fanout/run

Runs the fan-out manually. Requires x-admin-token. The cron also runs the fan-out every minute.

IPTV

All require paid access.

RouteDescription
GET /api/iptv/catalog/:countryChannel list for the country
GET /api/iptv/access/:countryAccess/signature URLs
GET /api/iptv/stream/:idRedirects (302) to the authorized stream

Country with two lowercase letters (br, us, es, …).

Radio

GET /api/radio/stations/:country

Country with two letters (br, us, …) or all. Returns up to 100 stations, without broken stations, sorted by clicks. Requires paid access.

News

GET /api/news/:country

ParameterDefaultLimit
page11–100
limit201–50
format—summary only on the article

Countries: br, us, gb, fr, de, es, it, pt, jp, cn.

The response includes country, page, limit, total, hasMore, updatedAt, stale, refreshError and articles[].

GET /api/news/:country/:articleId?format=summary

Returns the complete article (summary of up to 3,000 characters) with licenseName and licenseUrl.

Football

GET /api/football/:country

Countries: br, it, de, es, gb, fr, pt, nl.

Query refresh=1 forces an update. The response includes competition, season, standings, results and upcoming matches, plus source and cached.

Games

RouteParameters
GET /api/games/popularpage, pageSize, sort (popular, recent, upcoming), genre, language
GET /api/games/searchq (required), page, pageSize, genre, language
GET /api/games/genres—
GET /api/games/:idlanguage

pageSize is limited by the Worker; the app uses 30. Errors: invalid_page, invalid_page_size, invalid_sort, invalid_genre, invalid_query, games_upstream_failed.

TMDb

All require paid access. page is normalized; language defaults to pt-BR; region defaults to BR.

RouteRequired query
GET /api/search/moviequery (or q)
GET /api/search/tvquery (or q)
GET /api/search/multiquery (or q)

Lists

RouteNote
GET /api/movies/popularalso accepts /api/movie/popular
GET /api/movie/upcomingupcoming releases
GET /api/tv/popularpopular series
GET /api/movies/trendingweekly movie trending
GET /api/tv/trendingweekly series trending
GET /api/trending/:mediaType/:timeWindowmovie/tv/all, day/week
GET /api/tv/airing_todayairing today

Discover

GET /api/discover/movie:

page, region, with_genres, without_genres, primary_release_date.gte, primary_release_date.lte, sort_by, include_adult, language.

GET /api/discover/tv:

page, with_genres, with_original_language, sort_by, include_adult, language.

Details

RouteDescription
GET /api/movie/:idMovie details
GET /api/movie/:id/videosTrailers (en-US fallback)
GET /api/movie/:id/creditsCast
GET /api/tv/:idSeries details
GET /api/tv/:id/season/:nSeason
GET /api/tv/:id/creditsCast
GET /api/person/:idPerson (en-US biography fallback)
GET /api/person/:id/combined_creditsCredits
GET /api/person/:id/external_idsExternal IDs

Discontinued routes

/api/license/* and the public activation key routes return 410 Gone:

  • /api/activation-keys/claim
  • /api/activation-keys/validate-and-claim
  • /api/activation-keys/validate

The current flow is Stripe + entitlement.

Errors and CORS

  • Errors follow { "ok": false, "error": "code" } or { "error": "message" }.
  • ALLOWED_ORIGINS defines the allowed origins; empty uses the Worker's default policy.
  • OPTIONS returns 204 with CORS headers.
  • Per-IP limits use Cloudflare rate limit bindings.

Cron

wrangler.toml schedules * * * * * (every minute). On each run:

  • notification fan-out;
  • every 20 minutes: news warm-up, summary backfill, games cache pruning and TMDb warm-up;
  • every hour: cleanup of news from previous days.

↑ Back to top

Reference

Version history

4.4.13 — 03/11/2026

versionCode 19

  • Fixed cover display for PNG, WebP and GIF files in the manga catalog; previously only .jpg files were accepted.
  • The chapter list now follows the MangaDex feed total (capped at 10,000) instead of stopping at 2,000. Long series no longer lose chapters.
  • The website now uses its own proxy for the API and covers, with a server User-Agent; the manga section loads again.
  • Fixed horizontal overflow of manga descriptions with long URLs on the website.
  • Published APK: watchverse_4.4.13.apk, 120,693,331 bytes.
  • SHA-256: e0bf63a1495a551505e5c3071c4118f5e58d08a34a63b7ecf67e42e753465b12.
  • No changes to authentication, licensing, payments, media or backend.

4.4.12 — 02/11/2026

versionCode 18

  • Help Center also published in English, at https://watchverse.fun/documentation.
  • The Help button now opens the edition matching the app language: /documentacao for pt-BR and /documentation for en-US.
  • Both pages have a language switcher and reciprocal hreflang for SEO.
  • No changes to authentication, licensing, payments, media or backend.

4.4.11 — 02/11/2026

versionCode 17

  • The Help item in Profile now opens this Help Center in an internal WebView (https://watchverse.fun/documentacao?embed=1 for Portuguese and /documentation?embed=1 for English), with an error fallback, retry button and option to open in the browser.
  • The documentation is now generated from docs/ (npm run docs:build) and published on the site in Portuguese and English.
  • No changes to authentication, licensing, payments, media or backend.

4.4.10 — 01/11/2026

versionCode 16

  • Fixed the radio player opened from Favorites: the RadioPlayer route is now stacked over LiveHome with initial: false. The back button and tapping the Live tab again return to the hub.
  • Published APK: watchverse_4.4.10.apk, 120,680,187 bytes.
  • SHA-256: c6875d1946d86d48d4ea7b08991dfcc426917c18dab7f6850e2160ec2b040b8c.
  • Valid v3 signature; certificate 19d85449…6f7f.

4.4.9 — 01/11/2026

versionCode 15

  • Signature changed to v1 off, v2 and v3 enabled in the DSL; AGP produced a v3-only APK, accepted by minSdk 29.
  • Removed unused exported components:
    • com.google.android.gms.auth.api.signin.RevocationBoundService
    • androidx.profileinstaller.ProfileInstallReceiver
  • Kept FirebaseInstanceIdReceiver (FCM).
  • Created the MobSF justified-suppression document.
  • SHA-256: 2bf8f9fbc1b3ab7ab6e63dbd6d7837ed8be40a1b42e97c25eaed01dcf2d5b55d.

4.4.8 — 01/11/2026

versionCode 14

  • NewsService now accepts summaries of up to 3,000 characters, aligned with the Worker contract (MAX_ARTICLE_BODY_CHARS).
  • Regression test added; newsService.test.ts 17/17.
  • News restored and validated (GET /api/news/br 200, 15 articles).

4.4.7 — 01/11/2026

  • @expo/dom-webview excluded from autolinking because it is dead code.
  • react-native-webview kept with dangerous flags disabled and HTTPS/host validation.
  • MobSF medium alerts documented.
  • Remaining MD5 and SHA-1 kept for non-cryptographic use or specification requirements.

4.4.6 — 30/10/2026

  • Cleartext HTTP removed from the release build; localhost and 10.0.2.2 moved to the debug configuration.
  • IPTV channels without HTTPS now use HTTPS mirrors or are discarded.
  • Storage and media permissions removed from the manifest.
  • allowBackup=false and restricted backup rules.
  • HTML banner allowlist tightened.
  • Player iframe src escaped.
  • Local user data cleared on sign-out.
  • IP rate limiting in the OTP flow.

4.4.5 — 30/10/2026

  • CAMERA permission removed (it was not used).
  • AES-CBC with PKCS5/PKCS7 in androidx.biometric kept: encryption only, with no decryption of attacker input.
  • minSdk raised to 29 (Android 10).

4.4.3 — 30/10/2026

  • minSdk 24 → 29.
  • WebView remote debugging disabled in release.

Documentation — 02/11/2026

  • Created the Help Center in Markdown as a single source.
  • Published at https://watchverse.fun/documentacao with an English edition at /documentation.
  • The Help button in Profile now opens the documentation in a WebView, with an error fallback.
  • Added user, developer, API, security and changelog chapters.
  • Internal security recommendations document kept separate and not published.

↑ Back to top

Security

Security model

Principles

WatchVerse follows four principles:

  1. The server decides access. The app is not the final authority on license or device; the Worker validates token, email, entitlement and deviceId.
  2. Least privilege. The manifest declares only the necessary permissions (internet, background media, biometrics, vibration).
  3. Secure transport. All traffic is HTTPS; cleartext is blocked in release.
  4. Small surface. Unused exported components were removed; WebViews validate hosts; there is no third-party ad SDK.

Layers

1. Device and app integrity

In a production build (!__DEV__ and outside Expo Go), AppSecurityService checks:

CheckEffect
Development environment / Expo GoBlocks
EmulatorBlocks
Root (RootBeer + custom detection)Blocks
Android signatureCompares with the expected SHA-256; blocks if it differs
Updates runtimeChecks consistency when enabled

Blocking shows SecurityBlockedScreen with the reason. Checks run at startup and have a timeout so they do not freeze the app.

Limit: no local check is invulnerable to an attacker with full control of the device. They raise the cost and cover ordinary users, but the access decision remains on the server.

2. Authentication

  • Google/Gmail sign-in only.
  • The Worker validates the Firebase ID token against Google's public keys.
  • Requires email_verified and a Gmail domain.
  • The Worker session uses a 32-byte token (64 hex) stored in SecureStore.
  • The OTP code has 8 digits, 5-minute validity, 3 attempts, 60-second resend and a limit of 5 per hour.
  • OTP and session token are stored as hashes; comparison is constant-time.

3. Authorization and license

  • The entitlement is written to Firebase by the Stripe webhook, not by the app.
  • The Worker checks amount (100 cents), currency (usd) and session identity.
  • The webhook validates the Stripe signature and is idempotent by session.id.
  • The account is linked to a device through deviceId; another device receives DEVICE_MISMATCH.
  • Paid-content routes require an active entitlement and the same deviceId.

4. Transport

  • Release network_security_config.xml: cleartextTrafficPermitted="false", with no exceptions.
  • The debug file has a separate configuration for localhost/10.0.2.2 and is not included in release.
  • IPTV channels that exist only over HTTP use HTTPS mirrors or are discarded.
  • Worker requests have a timeout and response size limit.

5. Storage

DataWhereProtection
Session tokenSecureStoreAndroid Keystore
Biometric preferenceSecureStoreAndroid Keystore
Cached licenseSecureStoreAndroid Keystore
LanguageSecureStoreAndroid Keystore
Favorites, progress, cachesAsyncStorageApp sandbox
BackupDisabledallowBackup=false

On a non-rooted device, other apps cannot access the sandbox. Sign-out clears the user's local data.

6. Biometrics

  • Uses BiometricPrompt with CryptoObject.
  • Requires strong biometrics enrolled.
  • Protects the already authenticated session; it does not replace Google sign-in.
  • The key stays in the Keystore and the app only uses it to build the CryptoObject.

The AES-CBC/PKCS7 use in that AndroidX library is only to encrypt with the Keystore key. There is no decryption of attacker-controlled data, therefore no padding oracle.

7. WebViews

WebViewContentControls
PlayerProvider iframeHTTPS, host allowlist, no bridge, escaped src
CheckoutStripeFixed URL, HTTPS
BannerBackend HTMLrestricted origins (about:blank, data:, blob:, HTTPS)
DocumentationOfficial siteHTTPS, fixed host, no bridge

Dangerous settings are off by default in the library version used:

  • allowFileAccess=false
  • allowContentAccess=false
  • allowFileAccessFromFileURLs=false
  • allowUniversalAccessFromFileURLs=false
  • mixedContentMode=NEVER_ALLOW
  • remote debugging disabled in release

External links go through validation: only absolute http(s); intent:, javascript:, file: and host-relative URLs are refused.

8. Screen capture

expo-screen-capture prevents screen capture and recording while the app is in use. This reduces visual content leaks, but does not prevent photos taken with another device.

9. Backend

  • D1 queries are parameterized.
  • Secrets live in Worker bindings, never in the APK.
  • IP rate limiting on OTP, tracking, device registration and notification clicks.
  • CORS configurable through ALLOWED_ORIGINS.
  • Logs do not print keys; provider errors are logged without the URL containing credentials.
  • Legacy activation key routes return 410.

What the app does not do

  • It does not bypass DRM, geo-blocking or provider logins.
  • It does not download protected content.
  • It does not collect the Google password (the flow is delegated to Google).
  • It does not store card data (Stripe hosts the checkout).
  • It does not guarantee that third-party content is safe or available.

Accepted residual risks

RiskSituationMitigation
Sophisticated root/emulatorLocal detectors can be bypassedThe server keeps validating access
External video providersThey may collect data and show their own adsWebView without bridge and with allowlist
Third-party news contentAutomatic summaries may be wrongLink to the source and credit
Unstable IPTV streamsAvailability variesStream health, fallback and cache
Capture by an external cameraOutside the app's controlNotice in the policy
expo-dev-client present in the bundleNo launcher activity, unusable by the userDependency hygiene pending item
Android push missingNo registered tokenIn-app notifications

Internal recommendations

Operational, hardening and incident response guidance lives in an internal document maintained by the team, not published on the site.

Reporting a vulnerability

Send an email through the form at https://watchverse.fun/contato.html, describing the problem and the steps to reproduce it. Do not include other people's data, tokens or account content. Receipt is confirmed and the fix is prioritized according to impact.

↑ Back to top