feat: migrate InkNest to React Native 0.84 / React 19 and add Novels reading (v1.5.0) - #73
feat: migrate InkNest to React Native 0.84 / React 19 and add Novels reading (v1.5.0)#73pushpender-singh-ap wants to merge 11 commits into
Conversation
- Bump React Native and related packages from 0.84.0 to 0.84.1 - Update app version to 1.4.9 (build 38) - Add InkNest-Externals as git submodule - Configure Android release build signing with keystore - Add POST_NOTIFICATIONS and VIBRATE permissions to Android manifest - Update Android icons from PNG to WebP format with adaptive icon support - Expand iOS app icon set for all device types and scales - Add CI post-clone script for Homebrew and dependency setup - Allow app.json to be tracked in git - Update iOS Pods dependencies to 0.84.1
Restores the complete InkNest feature set on top of the fresh RN 0.84.1 project setup, replacing the placeholder new-app screen with the real application. 183 files changed (~44k insertions). App bootstrap & infrastructure - Replace App.tsx placeholder with App.js wiring up Redux store + persist, navigation container, gesture handler, safe-area, toast, and providers - index.js registers the root component, background handlers, and the notification helpers (now MMKV-backed instead of AsyncStorage) - Add Redux Toolkit store, root reducer, redux-persist with MMKV storage, and an axios interceptor controller (src/Redux/*) Comics - Home, Details, Reader (ComicBook), Bookmarks/History, Library, Search, See-All, and Sources screens (src/Screens/Comic/*) - Page-flip reader (PageFlipper), zoomable Gallery, vertical reader, and offline download manager - HTML source parsers for home/details/chapter/search (src/Redux/Actions/parsers/*) Novels - Full novel module: Home, Details, Reader (text/web), Library, Search, See-All, source selector, and offline storage/download manager (src/Screens/Novel/*) - Novel parsers, APIs, and constants including WTR-Lab mode support Auth, accounts & community - Firebase integration: app, auth, firestore, analytics, crashlytics, messaging, in-app-messaging, perf (GoogleService-Info.plist, firebase.json) - Google Sign-In and Apple authentication, login prompt and user avatar UI - Community actions (Redux) for shared/social features Notifications & source status - Notifications screen and notification helpers - Source status tracking with banner/notification UI and useSourceStatus hook Navigation & shared UI - App/Bottom navigation, navigation service, and route constants - Reusable UIComp library (headers, footers, cards, skeletons, markdown, error states, loading, gallery popup, parallax carousel) - ForceUpdate flow, v1.4.6 walkthrough, and About/Settings/Update screens Tooling & dependencies - Add ~40 runtime deps: Reanimated 4, gesture-handler, screens, MMKV, Firebase suite, redux toolkit/persist, axios, paper, svg, webview, video, faster-image (patched), vector-icons, device-info, dotenv, configcat, etc. - Babel: add export-namespace-from + dotenv plugins; tsconfig path tweak - iOS: update Podfile/Podfile.lock, project.pbxproj, AppDelegate, privacy manifest; Android: build.gradle and launcher icon updates - Add LICENSE, CODE_OF_CONDUCT, CONTRIBUTING, example.env; refresh yarn.lock Testing status - Tested on a physical iOS device — the app builds, launches, and the core flows work fine. - KNOWN ISSUE: react-native-vector-icons are not rendering correctly and still need to be fixed. - NOT YET TESTED on Android — build and runtime behavior there is unverified. - Treat this as a work-in-progress checkpoint: other unknown issues may still be pending and the build needs a deeper end-to-end pass.
… to MMKV Strip out all Google AdMob / react-native-google-mobile-ads usage across the app and replace AsyncStorage with MMKV for gravity-scroll settings persistence. Ad removal: - Delete Ads/BannerAds.js the AdBanner component and Ads/AppendAd.js the helper that injected type ad items every 4th position in lists. - Remove the rewarded-ad flow: drop showRewardedAd from Redux/Actions/Download.js along with its commented-out RewardedAd implementation and @env imports. - Stop calling showRewardedAd on source-open in WebViewComponent and LinkListScreen, and remove the AnimeAdbanner header from LinkListScreen. - newCard.js no longer renders the AdBanner for item.type === ad; it now returns null for ad placeholders. - MangaBook.js: remove the inline BannerAd, BannerAdSize, and AdBanner imports. - Drop the showAds flag from FREE/PREMIUM/PRO tiers in SubscriptionFeatures.js. - Remove the react-native-google-mobile-ads block app IDs, SKAdNetwork items from app.json and all AdMob unit-id env vars banner/interstitial/reward and the IOS_GOOGLE_CLIENT_ID trailing-newline fix from .env. Storage migration: - MangaBook.js now reads/writes gravity-scroll settings and the walkthrough-seen flag via mmkvStorage synchronous instead of AsyncStorage, removing the AsyncStorage dependency from this screen.
Register bundled vector icon font files in Info.plist so iOS can load React Native icon fonts correctly. Add an empty Swift source file and bridging header to enable Swift support in the iOS target, and update the Xcode project references. Refresh Podfile.lock with the current CocoaPods version.
- add a persistent hidden WebView proxy that serializes protected GET and POST requests, navigates through Cloudflare challenges, and returns rendered HTML - add a verification prompt and modal WebView flow for manually completing Cloudflare checks when automatic verification cannot finish - capture and persist Cloudflare clearance cookies and the matching user agent, then refresh affected screens after verification succeeds - attach clearance headers to protected comic cover and reader image requests - update home, comic detail, chapter, and advanced-search parsers for the 2026 Read Comics Online site redesign - replace the limited autocomplete search with the CSRF-protected advanced-search form to return complete catalog results - add loading, failure, Cloudflare-protected, and retry states to the comic library - fix comic detail chapter selection so the complete filtered chapter list is returned outside the recent tab - keep transient Cloudflare prompt and refresh state out of Redux persistence - add the native cookie manager dependency and update iOS pod resolution - enable Firebase Crashlytics in the Android Gradle configuration - bundle vector icon fonts in Android assets - update react-native-permissions to 5.6.0 - patch react-native-sensors to replace deprecated JCenter repositories with Maven Central
- remove ComicHubFree from supported comic hosts and source metadata - delete ComicHubFree search configuration and result parsing - stop requesting and displaying ComicHubFree results in global comic search - remove ComicHubFree search filters, result counts, URL recognition, and fallback selection - delete ComicHubFree home-page request groups and scraping selectors - remove ComicHubFree comic detail, chapter, and reader parsing configurations - remove the ComicHubFree-specific URL transformation - change the default comics home source to ReadComicsOnline - standardize the home section title as Latest Release
- replace the native loading modal with an absolute-positioned overlay to avoid frozen touch handling during iOS navigation transitions and prevent Android back-button interception - render the loading overlay only while a request is actively loading - add a 30-second Axios timeout so stalled API requests cannot leave screens stuck indefinitely in a loading state - remove the Community tab from the bottom navigation - remove the Read Manga shortcut from the Settings screen - update Firestore participant queries to use the modular documentId helper - update the About Us contact email to inknest@capacity.rocks - remove leftover comic details debug logging
…ueries Import the documentId helper directly from @react-native-firebase/firestore and use it when querying participant profiles by user document ID. This replaces the namespaced firestore.FieldPath.documentId() call, ensuring the participant preview query uses the supported modular Firestore API while preserving the existing batching, profile mapping, and result ordering.
PR Summary by QodoMigrate to RN 0.84/React 19, add Novels + Cloudflare verification flow
AI Description
Diagram
High-Level Assessment
Files changed (135)
|
Code Review by Qodo
1. Cloudflare host match too loose
|
| /** True if the URL targets a Cloudflare-challenged host we manage clearance for. */ | ||
| export const isCloudflareProtectedUrl = url => | ||
| typeof url === 'string' && CF_PROTECTED_HOSTS.some(host => url.includes(host)); |
There was a problem hiding this comment.
1. Cloudflare host match too loose 🐞 Bug ⛨ Security
isCloudflareProtectedUrl() uses substring matching (url.includes(host)), so non-Cloudflare URLs that merely contain the host string can be misclassified and routed through the Cloudflare WebView proxy and/or have clearance headers attached. This can cause incorrect navigation/request behavior and may leak clearance headers if a misclassified URL is ever used.
Agent Prompt
### Issue description
`isCloudflareProtectedUrl()` uses `url.includes(host)`, which does not validate the actual URL hostname and can misclassify arbitrary URLs.
### Issue Context
This function gates:
- axios adapter routing to the hidden WebView proxy
- attaching `Cookie`/`User-Agent` headers for protected resources
### Fix Focus Areas
- src/Utils/cloudflareClearance.js[35-37]
- src/Redux/Controller/Interceptor.js[22-33]
- src/Components/UIComp/Image.js[20-42]
- src/Components/CloudflareProxy/index.js[111-121]
### Proposed fix
- Replace substring matching with strict URL parsing:
- `const {hostname} = new URL(url)` (guard try/catch)
- match `hostname === 'readcomicsonline.ru'` (or `hostname.endsWith('.readcomicsonline.ru')` if subdomains are intended)
- Consider tightening WebView `originWhitelist` to `['https://readcomicsonline.ru/*']` (and any other explicitly supported CF origins) to reduce blast radius if a URL ever slips through.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| android:usesCleartextTraffic="${usesCleartextTraffic}" | ||
| android:supportsRtl="true"> |
There was a problem hiding this comment.
2. Unresolved manifest placeholder 🐞 Bug ☼ Reliability
AndroidManifest.xml sets android:usesCleartextTraffic="${usesCleartextTraffic}" but the project does
not define a manifestPlaceholder for usesCleartextTraffic, which can break Android manifest
merging/builds. If later defined incorrectly, it could also unintentionally enable cleartext traffic
in release.
Agent Prompt
### Issue description
The Android manifest references an unresolved placeholder `${usesCleartextTraffic}`.
### Issue Context
No `manifestPlaceholders` configuration is present in `android/app/build.gradle`, so this placeholder is not set by default.
### Fix Focus Areas
- android/app/src/main/AndroidManifest.xml[14-15]
- android/app/build.gradle[77-116]
### Proposed fix
Choose one:
1) **Hardcode a safe value** (recommended):
- Set `android:usesCleartextTraffic="false"` in the main manifest.
- If debug needs cleartext, add a debug manifest overlay or build-type specific manifest.
2) **Use manifestPlaceholders explicitly**:
- In `android/app/build.gradle`:
- `defaultConfig { manifestPlaceholders = [usesCleartextTraffic: "false"] }`
- Optionally override in `buildTypes.debug { manifestPlaceholders.usesCleartextTraffic = "true" }` if required.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const parseForm = body => { | ||
| const out = []; | ||
| String(body || '') | ||
| .split('&') | ||
| .filter(Boolean) | ||
| .forEach(pair => { | ||
| const i = pair.indexOf('='); | ||
| const k = decodeURIComponent(pair.slice(0, i)); | ||
| const v = decodeURIComponent(pair.slice(i + 1).replace(/\+/g, ' ')); | ||
| out.push(`<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}">`); |
There was a problem hiding this comment.
3. Proxy post parsing can throw 🐞 Bug ☼ Reliability
webviewProxy.parseForm() calls decodeURIComponent() on untrusted strings without guarding, so malformed percent-encoding can throw synchronously. Because pump() sets proxy state (busy/current) and does not catch around parseForm(), an exception can wedge the proxy and strand subsequent Cloudflare requests.
Agent Prompt
### Issue description
`parseForm()` can throw (notably via `decodeURIComponent()`), and `pump()` does not guard against that; this can leave `busy/current` set and stall the request queue.
### Issue Context
The proxy is a singleton used by the axios adapter for Cloudflare-protected pages; wedging it breaks all such page loads until app restart/unmount.
### Fix Focus Areas
- src/Utils/webviewProxy.js[52-64]
- src/Utils/webviewProxy.js[73-91]
### Proposed fix
- Wrap `parseForm(body)` in `pump()` with `try/catch` and call `settle(null, '...')` (or a dedicated cleanup) on error.
- Make `parseForm` more defensive:
- skip pairs without `=`
- wrap `decodeURIComponent` per field; on decode failure, fall back to raw strings
- optionally accept `URLSearchParams` or object bodies explicitly instead of stringifying arbitrary objects.
- Ensure a `finally` path resets `busy/current` for any synchronous error before/without injecting JS.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
|
||
| const filters = parseAdvancedSearchFilters($, config); | ||
|
|
||
| dispatch(checkDownTime({filters})); | ||
| dispatch(checkDownTime({filters}, source)); | ||
|
|
There was a problem hiding this comment.
4. Success path calls checkdowntime 🐞 Bug ≡ Correctness
getAdvancedSearchFilters() dispatches checkDownTime({filters}, source) on success, but checkDownTime
only clears downtime/records success when the first argument is null/undefined. This can leave
downTime/source success tracking stale even after a successful filters fetch.
Agent Prompt
### Issue description
`checkDownTime` is an error/success handler that treats any truthy first argument as an error path. Passing `{filters}` on success prevents the intended success behavior.
### Issue Context
`checkDownTime(!error)` is the only path that dispatches `DownTime(false)` and records per-source success.
### Fix Focus Areas
- src/Redux/Actions/GlobalActions.js[281-289]
- src/Redux/Actions/utils/errorHandlers.js[38-47]
### Proposed fix
- Replace `dispatch(checkDownTime({filters}, source))` with one of:
- `dispatch(checkDownTime(null, source))` (preferred if you want the per-source success bookkeeping)
- or explicitly `dispatch(DownTime(false))` and call `recordSourceSuccess(source)` if that’s the intention.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| /** | ||
| * Cancel a download | ||
| */ | ||
| cancelDownload(novelLink) { | ||
| this.downloads.delete(novelLink); | ||
| this.notifyListeners(novelLink, DownloadStatus.IDLE, 0); | ||
| } |
There was a problem hiding this comment.
5. Cancel download not honored 🐞 Bug ≡ Correctness
NovelDownloadManager.cancelDownload() removes the download entry but downloadChapters() never checks for cancellation and only stops on PAUSED, so it continues downloading/saving chapters after the user cancels. This makes cancel ineffective and can waste bandwidth/storage.
Agent Prompt
### Issue description
`cancelDownload()` doesn't stop an in-flight `downloadChapters()` loop.
### Issue Context
The loop checks only `DownloadStatus.PAUSED`. After cancel, `this.downloads.get()` becomes `undefined`, so the loop continues.
### Fix Focus Areas
- src/Screens/Novel/Utils/DownloadManager.js[116-123]
- src/Screens/Novel/Utils/DownloadManager.js[220-223]
### Proposed fix
- Introduce a per-download cancellation flag/token, e.g. store `{status: 'canceled'}` or `{abort: AbortController}`.
- In the loop, before each chapter (and after network calls), check for canceled and return early.
- Optionally prevent writes (`saveChapterContent`) when canceled mid-flight.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| export function getNovelPath(novelLink) { | ||
| const slug = novelLink.split('/book/')[1]?.replace(/\//g, '-') || 'unknown'; | ||
| return `${NOVELS_ROOT}/${slug}`; | ||
| } |
There was a problem hiding this comment.
6. Offline novel slug collisions 🐞 Bug ☼ Reliability
OfflineStorage.getNovelPath() falls back to 'unknown' when novelLink doesn't contain '/book/', causing multiple novels to share the same offline directory and overwrite each other. Other novel UI code already anticipates links without '/book/', so this fallback is reachable.
Agent Prompt
### Issue description
Non-`/book/` novel links map to the same `unknown` directory, corrupting offline metadata/chapters.
### Issue Context
Elsewhere, novel list keys fall back to using the full URL when `/book/` is missing, indicating this can occur in practice.
### Fix Focus Areas
- src/Screens/Novel/Utils/OfflineStorage.js[29-32]
- src/Screens/Novel/Components/NovelList.js[19-26]
- src/Screens/Novel/SeeAll/index.js[25-31]
### Proposed fix
- Derive the directory name from a collision-resistant identifier:
- Prefer hostname + pathname normalized, then sanitize to safe filename chars, OR
- Hash the full `novelLink` (e.g., SHA-1/MD5) and use that as the directory key.
- If `novelLink` is missing/invalid, fail explicitly instead of writing to a shared fallback directory.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
This PR rebuilds InkNest on a fresh React Native 0.84.1 project base (previously 0.76.9) and ships v1.5.0 (
versionCode 39, previously 1.4.5 / 34). Rather than upgrading in place, the app was re-scaffolded from the RN 0.84 template and the full app was reimplemented on top of it, which is why the diff is large.Alongside the platform migration, this PR introduces a complete Novels section, restores the Read Comics Online source with Cloudflare challenge handling, removes AdMob entirely, and migrates the whole app to the Firebase v24 modular API.
Platform / Tooling Migration
@react-native/*0.84.1 and CLI 20.x tooling)nodeLinker: node-modules@types/react19,react-test-renderer19react-native-workletsdependencyreact-native-sensors@7.3.6and@candlefinance/faster-image@1.7.2build.gradle/manifest cleanup, updated launcher icons,react-native-permissions5.6.0New Feature: Novels
A full novel-reading experience added under
src/Screens/Novel/:TextReaderandWebReader(WebView-based), plus a WTRLab mode selectorDownloadManager,OfflineStorage)Comic Sources
cloudflareClearance.js— WebView-based CF Managed Challenge clearance flowwebviewProxy.jsand shareduserAgent.jsfor challenge-protected requestssourceStatus.js+useSourceStatushook to track per-source availability@preeternal/react-native-cookie-managerRemovals
react-native-google-mobile-adsand all ad placements)docs/)Fixes
documentId()helperTesting
yarn android --active-arch-only)