Convert web pages to EPUB. Send them to your e-reader. All over WiFi.
CrossX is a native SwiftUI app for iOS, iPadOS, and macOS that converts any web page into an EPUB 2.0 e-book and transfers it to an Xteink device e-reader over its local WiFi hotspot. No cloud services, no accounts, no subscriptions — just paste a URL, tap convert, and read.
Now available on the App Store — free to download.
The app supports both Stock and CrossPoint firmware variants with automatic device detection, includes a full on-device file manager, and ships with an iOS Share Extension so you can send pages directly from Safari.
Features · Screenshots · How It Works · Getting Started · Device Setup · Architecture · Roadmap
- URL-to-EPUB pipeline — paste any web page URL and get a properly formatted EPUB 2.0 e-book
- Dual content extraction — fast SwiftSoup heuristic extraction with automatic Readability.js fallback for complex pages
- Twitter/X support — dedicated extractor using the fxtwitter API for tweet threads
- Auto chapter splitting — long articles are split at
<h2>headings or by paragraph count (50 max per chapter) - HTML sanitization — strips scripts, forms, media, styles, and images for clean text-only EPUBs
- In-memory generation — no temp files; the entire EPUB is built as a
Dataobject via ZIPFoundation - Smart filenames — generated as
Title - Author - domain - YYYY-MM-DD.epub
- Dual firmware support — works with both Stock firmware (
192.168.3.3) and CrossPoint firmware (192.168.4.1/crosspoint.local) - Auto-detection — probes both firmware endpoints concurrently on connect and caches the result
- mDNS/Bonjour — CrossPoint firmware is discovered via
crosspoint.localwith static IP fallback - Retry logic — 2 automatic retries with 1-second delay on transient failures
- Upload progress — real-time progress tracking via
URLSessionUploadTaskdelegate - Connection status — persistent status bar showing firmware version, IP, WiFi mode, signal strength, free heap, and uptime
- Browse device storage — navigate directories with breadcrumb trail
- Upload files — supports
.epub,.xtc,.bump, and.txtformats - Create folders — with filesystem-safe name validation
- Delete files and folders — with confirmation dialogs
- Move files — directory picker for reorganizing content (CrossPoint firmware only)
- Rename files — edit filename stem while preserving extension (CrossPoint firmware only, coming soon)
- Unified timeline — merges EPUB conversion history and file manager operations into a single chronological view
- Filtering — switch between All, Conversions, File Activity, and Queue
- Search — full-text search across all activity events
- Granular clear — clear conversions only, file activity only, or everything
- Expandable detail rows — tap to see full URLs, error messages, and metadata
- Send from Safari — use the iOS Share Sheet to convert any web page without opening CrossX
- Auto-detect device — the extension finds your X4 automatically
- Fallback to local save — if the device isn't connected, the EPUB is saved locally
- Full pipeline — runs the complete fetch → extract → build → send flow in the extension
- Offline queuing — EPUBs converted while the device is disconnected are saved to disk and queued for later sending
- Auto-prompt on connect — when the device connects, an alert offers to send all queued items at once
- Queue management — view queued items in the Convert tab, remove individual items, or clear the entire queue from Settings
- Persistent storage — queued EPUBs survive app restarts; stored in Application Support with SwiftData tracking
- Batch sending — sends queued items sequentially with progress indicator, logs results to activity history
- Convert from Shortcuts — use the "Convert to EPUB & Add to Queue" action in the Shortcuts app
- Share Sheet integration — create a Shortcut with "Show in Share Sheet" to convert pages directly from Safari
- Background execution — conversions run without opening the app; results are queued automatically
- Siri voice support — say "Convert a page with CrossX" to convert by voice
- Rich feedback — shows article title, file size, and queue count on completion
- Setup guide — in-app guide in Settings walks you through enabling the Share Sheet shortcut
- Native multiplatform — single codebase builds natively for iOS, iPadOS, and macOS (not Mac Catalyst)
- Platform-adaptive UI — tab bar bottom accessory on iOS, Xcode-style status bar on macOS
- Liquid Glass design — leverages iOS 26 / macOS 26
.glassEffect()modifiers - Cross-platform clipboard — unified helper abstracts
UIPasteboard(iOS) andNSPasteboard(macOS)
┌──────────────────────────────────────────────────────────┐
│ CrossX App │
│ │
│ URL ──► Fetch HTML ──► Extract Content ──► Sanitize │
│ │ │ │ │
│ │ SwiftSoup (fast) │ │
│ │ or │ │
│ │ Readability.js (fallback) │ │
│ │ or │ │
│ │ Twitter API (tweets) │ │
│ │ ▼ │
│ │ Build EPUB │
│ │ (in-memory ZIP) │
│ │ │ │
│ │ ┌──────────┴─────────┐│
│ │ │ ││
│ ▼ Device connected? ││
│ URLSession │ ││
│ ┌─────┴─────┐ ││
│ Yes No ││
│ │ │ ││
│ multipart Queue to disk ││
│ POST (send later) ││
└──────────────────────────┬───────────────┬──────────────┘│
│ │
WiFi Hotspot App Support/
│ EPUBQueue/
▼
┌─────────────────────────┐
│ Xteink X4 │
│ E-Reader │
│ │
│ Stock: 192.168.3.3 │
│ CrossPoint: 192.168.4.1 │
│ (or mDNS) │
└─────────────────────────┘
| Requirement | Version |
|---|---|
| Xcode | 26.0+ (beta) |
| iOS / iPadOS | 26.0+ |
| macOS | 26.0+ |
| Swift | 5 |
Note: This app targets the latest Apple platform SDKs. You need Xcode 26 beta or later to build.
git clone https://github.com/jtvargas/crosspoint-app.git
cd crosspoint-appopen SendToX4.xcodeprojXcode will automatically resolve Swift Package Manager dependencies (ZIPFoundation and SwiftSoup).
iOS Simulator:
xcodebuild -project SendToX4.xcodeproj \
-scheme SendToX4 \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
buildmacOS:
xcodebuild -project SendToX4.xcodeproj \
-scheme SendToX4 \
-destination 'platform=macOS' \
buildOr simply select your target device in Xcode and press Cmd+R.
See Device Setup below.
The Xteink X4 e-reader creates its own WiFi hotspot. CrossX communicates with it over plain HTTP on the local network.
- Power on your Xteink X4
- On your iPhone/iPad/Mac, go to Settings → WiFi
- Connect to the X4's WiFi network (the SSID varies by firmware)
The app will automatically detect which firmware your device is running:
| Firmware | IP Address | mDNS | Endpoints |
|---|---|---|---|
| Stock | 192.168.3.3 |
— | /list, /edit |
| CrossPoint | 192.168.4.1 |
crosspoint.local |
/api/files, /upload, /mkdir, /delete |
Open Settings (gear icon) to:
- Choose your firmware type manually (or leave on auto-detect)
- Set a custom IP address
- Configure destination folders for conversions and wallpapers
- Toggle optional features (File Manager, WallpaperX)
Network note: The app requires the
NSAllowsLocalNetworkingATS exception andcom.apple.security.network.cliententitlement for plain HTTP communication with the device. These are already configured in the project.
CrossX follows MVVM (Model-View-ViewModel) with protocol-oriented services:
Views → ViewModels → Services
│ │
│ ├─ DeviceService (protocol)
│ │ ├─ StockFirmwareService
│ │ └─ CrossPointFirmwareService
│ │
│ ├─ ContentExtractor (SwiftSoup)
│ ├─ ReadabilityExtractor (WKWebView)
│ ├─ TwitterExtractor (fxtwitter API)
│ ├─ EPUBBuilder (ZIPFoundation)
│ └─ WebPageFetcher (URLSession)
│
└─ SwiftData Models
├─ Article (conversion history)
├─ DeviceSettings (configuration)
├─ ActivityEvent (file operations log)
└─ QueueItem (EPUB send queue)
- Protocol-oriented device communication —
DeviceServiceprotocol with concrete implementations per firmware, enabling easy mocking and future firmware support - Dual content extraction — SwiftSoup for speed (primary), WKWebView + Readability.js for accuracy (fallback), Twitter API for tweets
- In-memory EPUB generation — no temporary files; all ZIP operations produce
Dataobjects directly - Offline queue — EPUBs are written to disk and tracked via SwiftData when the device is disconnected; batch-sent when it reconnects
- Headless Siri Shortcuts —
ConvertURLIntentruns the full conversion pipeline without opening the app, using its ownModelContextagainst the shared SwiftData store @MainActorby default — the project usesSWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; services are explicitly markednonisolatedto avoid stack overflows- Native multiplatform —
SDKROOT = autowith#if os(iOS)/#if canImport(UIKit)conditional compilation (not Mac Catalyst)
crosspoint-app/
├── SendToX4.xcodeproj/ # Xcode project (SPM dependencies, build settings)
├── Info.plist # ATS local networking exception
├── AGENTS.md # Developer reference (architecture, conventions, deep dives)
├── README.md # This file
├── LICENSE # MIT License
│
├── SendToX4/ # Main app target
│ ├── SendToX4App.swift # @main entry point, SwiftData ModelContainer setup
│ ├── SendToX4.entitlements # App Sandbox + network client + Siri
│ │
│ ├── Models/
│ │ ├── Article.swift # Conversion history model (URL, title, status, error)
│ │ ├── DeviceSettings.swift # Device config singleton (firmware type, IP, toggles)
│ │ ├── ActivityEvent.swift # File operation log (upload, mkdir, move, delete, queue)
│ │ └── QueueItem.swift # EPUB send queue model (file path, size, linked Article)
│ │
│ ├── Views/
│ │ ├── MainView.swift # Root tab view (Convert, History, File Manager, WallpaperX)
│ │ ├── ConvertView.swift # URL input, convert & send actions, share sheet
│ │ ├── HistoryView.swift # Unified activity timeline with filtering and search
│ │ ├── FileManagerView.swift # Device file browser with breadcrumbs
│ │ ├── FileManagerRow.swift # File/folder row with context menu
│ │ ├── SettingsSheet.swift # Device configuration form
│ │ ├── SettingsToolbarModifier.swift # Reusable gear button toolbar modifier
│ │ ├── DeviceStatusBar.swift # Device info bar (version, IP, RSSI, uptime)
│ │ ├── DeviceConnectionAccessory.swift # iOS bottom tab accessory (connect status)
│ │ ├── MacDeviceStatusBar.swift # macOS bottom status bar (Xcode-style)
│ │ ├── WallpaperXView.swift # Placeholder for future wallpaper feature
│ │ ├── MoveFileSheet.swift # Destination folder picker for move
│ │ ├── RenameFileSheet.swift # File rename with extension lock
│ │ └── CreateFolderSheet.swift # New folder name input with validation
│ │
│ ├── ViewModels/
│ │ ├── ConvertViewModel.swift # URL → EPUB → device pipeline orchestrator
│ │ ├── DeviceViewModel.swift # Connection state, auto-detection, upload progress
│ │ ├── FileManagerViewModel.swift # File browsing, CRUD operations, activity logging
│ │ ├── HistoryViewModel.swift # Search, delete, granular clear for history
│ │ └── QueueViewModel.swift # Queue management (enqueue, sendAll, remove, clear)
│ │
│ ├── Services/
│ │ ├── DeviceService.swift # Protocol + models (DeviceFile, DeviceStatus, DeviceError)
│ │ ├── StockFirmwareService.swift # Stock firmware implementation (192.168.3.3)
│ │ ├── CrossPointFirmwareService.swift # CrossPoint implementation (192.168.4.1 / mDNS)
│ │ ├── DeviceDiscovery.swift # Concurrent firmware auto-detection engine
│ │ ├── EPUBBuilder.swift # In-memory EPUB 2.0 ZIP builder
│ │ ├── EPUBTemplates.swift # EPUB XML templates (OPF, NCX, XHTML, CSS)
│ │ ├── ChapterSplitter.swift # Long content → multi-chapter splitting
│ │ ├── ContentExtractor.swift # SwiftSoup heuristic article extraction
│ │ ├── ReadabilityExtractor.swift # WKWebView + Readability.js fallback
│ │ ├── WebPageFetcher.swift # URLSession HTML fetcher with encoding detection
│ │ └── TwitterExtractor.swift # X/Twitter via fxtwitter API
│ │
│ ├── Intents/
│ │ ├── ConvertURLIntent.swift # App Intent: URL → EPUB → queue (Siri/Shortcuts)
│ │ └── CrossXShortcuts.swift # AppShortcutsProvider (Siri phrases)
│ │
│ ├── Utilities/
│ │ ├── HTMLSanitizer.swift # Strip unsafe HTML for text-only EPUB
│ │ ├── StringExtensions.swift # XML escaping, domain extraction, truncation
│ │ ├── FileNameGenerator.swift # EPUB filename from metadata
│ │ ├── ClipboardHelper.swift # Cross-platform clipboard (UIKit/AppKit)
│ │ ├── StorageCalculator.swift # Storage size calculations (DB, cache, queue, temp)
│ │ ├── ReviewPromptManager.swift # In-app review prompt after successful actions
│ │ └── DesignTokens.swift # AppColor design system (accent, success, error, warning)
│ │
│ ├── Resources/
│ │ └── readability.js # Mozilla Readability.js (bundled for WKWebView)
│ │
│ └── Assets.xcassets/ # App icon, AccentColor (teal light/dark)
│
└── SendToX4ShareExtension/ # iOS Share Extension target
├── Info.plist # Extension config (accepts 1 web URL)
└── ShareViewController.swift # Full pipeline: fetch → extract → EPUB → send/save
The conversion pipeline runs entirely in memory with no temporary files:
- Fetch —
WebPageFetcherdownloads the HTML viaURLSessionwith a Safari user-agent, encoding detection, and redirect following - Extract —
ContentExtractor(SwiftSoup) parses the DOM for article content using semantic selectors (<article>,[role=main],.post-content, etc.). If extraction fails (< 400 chars), falls back toReadabilityExtractor(WKWebView + Readability.js). Twitter/X URLs useTwitterExtractorvia the fxtwitter API - Sanitize —
HTMLSanitizerstrips all scripts, styles, forms, media, images, SVGs, iframes, event handlers, and data attributes. Links are converted to plain text for a clean reading experience - Build —
EPUBBuilderassembles the EPUB 2.0 package in memory:mimetype(uncompressed),META-INF/container.xml,content.opf,toc.ncx, and one or morechapter-N.xhtmlfiles. Long content is auto-split byChapterSplitterat<h2>boundaries or every 50 paragraphs - Send — The
Datablob is uploaded via multipart/form-data POST to the device's upload endpoint, with real-time progress tracking
CrossX uses a tiered extraction approach to handle the widest range of web pages:
| Tier | Extractor | Method | When |
|---|---|---|---|
| 1 | TwitterExtractor |
fxtwitter JSON API | Twitter/X status URLs |
| 2 | ContentExtractor |
SwiftSoup DOM parsing | All other URLs (primary) |
| 3 | ReadabilityExtractor |
WKWebView + Readability.js | Fallback when SwiftSoup extracts < 400 chars |
The SwiftSoup extractor uses a priority list of CSS selectors to find article content:
article, [role=main], .post-content, .entry-content, .article-body, #content, main, and more.
Metadata (title, author, description, language) is extracted from Open Graph tags, meta tags, and heading elements.
CrossX uses a minimal design token system with four semantic colors:
| Token | Color | Usage |
|---|---|---|
AppColor.accent |
Teal | Primary actions, navigation, icons |
AppColor.success |
Green | Successful operations, connected state |
AppColor.error |
Red | Errors, destructive actions, disconnected state |
AppColor.warning |
Orange | Warnings, pending states |
The AccentColor asset is set to teal with light and dark mode variants. The UI uses iOS 26 / macOS 26 Liquid Glass modifiers (.glassEffect()) for a translucent, modern appearance.
| Package | Version | Purpose |
|---|---|---|
| ZIPFoundation | >= 0.9.0 | In-memory EPUB ZIP archive creation |
| SwiftSoup | >= 2.6.0 | HTML parsing and content extraction |
Dependencies are managed via Xcode's Swift Package Manager integration. They resolve automatically when you open the project.
- WallpaperX — custom wallpaper upload and management for the X4
- Share Extension queue integration — update the iOS Share Extension to use the queue system instead of temp file saves
- File rename — currently disabled; waiting for CrossPoint firmware API stabilization
- Image support in EPUBs — optionally include images for richer e-books
- Batch conversion — convert multiple URLs in one session
- Reading list integration — import from Safari Reading List
- visionOS support — deployment target is already set; UI needs spatial adaptation
- Localization — multi-language support
Contributions are welcome! See CONTRIBUTING.md for the full guide, including:
- Development setup and build commands
- Code conventions and architecture rules
- PR process and checklist
- Common pitfalls to avoid
Quick start:
- Fork the repository
- Create a branch (
git checkout -b feature/my-feature) - Make your changes — follow the conventions in AGENTS.md
- Build both platforms —
xcodebuildfor iOS and macOS - Open a Pull Request — the PR template will guide you
This project is licensed under the MIT License — see the LICENSE file for details.
Built for the Xteink X4 e-reader community.
