310 lines
8.7 KiB
Markdown
310 lines
8.7 KiB
Markdown
|
|
# CrossPoint Companion Deep Link API
|
||
|
|
|
||
|
|
This document describes the deep link functionality that allows the CrossPoint Companion Android app to be launched from QR codes displayed on CrossPoint e-reader devices.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The CrossPoint firmware can generate QR codes containing deep link URLs. When scanned with a mobile device, these URLs launch the companion app directly to a specific tab and optionally auto-connect to the device.
|
||
|
|
|
||
|
|
## URL Scheme
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://<path>?<query_parameters>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Components
|
||
|
|
|
||
|
|
| Component | Description |
|
||
|
|
|-----------|-------------|
|
||
|
|
| `crosspoint://` | Custom URL scheme registered by the app |
|
||
|
|
| `<path>` | Target tab in the app (see [Path Mapping](#path-mapping)) |
|
||
|
|
| `<query_parameters>` | Optional device connection parameters |
|
||
|
|
|
||
|
|
## Path Mapping
|
||
|
|
|
||
|
|
The URL path determines which tab the app navigates to:
|
||
|
|
|
||
|
|
| Path | App Tab | Description |
|
||
|
|
|------|---------|-------------|
|
||
|
|
| `files` | Device | File browser for device storage |
|
||
|
|
| `library` | Library | Local book library |
|
||
|
|
| `lists` | Lists | Reading lists management |
|
||
|
|
| `settings` | Settings | App settings |
|
||
|
|
|
||
|
|
**Note:** Unknown paths default to the Library tab.
|
||
|
|
|
||
|
|
## Query Parameters
|
||
|
|
|
||
|
|
Query parameters provide device connection information for automatic connection:
|
||
|
|
|
||
|
|
| Parameter | Type | Default | Description |
|
||
|
|
|-----------|------|---------|-------------|
|
||
|
|
| `host` | string | *(required for auto-connect)* | IP address or hostname of the device |
|
||
|
|
| `port` | integer | `80` | HTTP API port |
|
||
|
|
| `wsPort` | integer | `81` | WebSocket port for file uploads |
|
||
|
|
|
||
|
|
## URL Examples
|
||
|
|
|
||
|
|
### Basic Navigation (No Auto-Connect)
|
||
|
|
|
||
|
|
Navigate to a specific tab without connecting to a device:
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://files
|
||
|
|
crosspoint://library
|
||
|
|
crosspoint://lists
|
||
|
|
crosspoint://settings
|
||
|
|
```
|
||
|
|
|
||
|
|
### Auto-Connect to Device
|
||
|
|
|
||
|
|
Navigate to Device tab and auto-connect:
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://files?host=192.168.1.100
|
||
|
|
crosspoint://files?host=192.168.1.100&port=80&wsPort=81
|
||
|
|
```
|
||
|
|
|
||
|
|
### Custom Ports
|
||
|
|
|
||
|
|
Connect to a device with non-default ports:
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://files?host=192.168.1.100&port=8080&wsPort=8081
|
||
|
|
```
|
||
|
|
|
||
|
|
### Hostname Instead of IP
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://files?host=crosspoint.local&port=80&wsPort=81
|
||
|
|
```
|
||
|
|
|
||
|
|
## Firmware Implementation
|
||
|
|
|
||
|
|
### QR Code Generation
|
||
|
|
|
||
|
|
The CrossPoint firmware should generate QR codes containing the deep link URL. Example format:
|
||
|
|
|
||
|
|
```
|
||
|
|
crosspoint://files?host=<device_ip>&port=<http_port>&wsPort=<ws_port>
|
||
|
|
```
|
||
|
|
|
||
|
|
Where:
|
||
|
|
- `<device_ip>` is the device's current IP address (e.g., from WiFi connection)
|
||
|
|
- `<http_port>` is the HTTP API port (default: 80)
|
||
|
|
- `<ws_port>` is the WebSocket port (default: 81)
|
||
|
|
|
||
|
|
### Example Firmware Code (C++)
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
String generateDeepLinkUrl(const char* path = "files") {
|
||
|
|
String url = "crosspoint://";
|
||
|
|
url += path;
|
||
|
|
url += "?host=";
|
||
|
|
url += WiFi.localIP().toString();
|
||
|
|
url += "&port=";
|
||
|
|
url += String(HTTP_PORT); // e.g., 80
|
||
|
|
url += "&wsPort=";
|
||
|
|
url += String(WS_PORT); // e.g., 81
|
||
|
|
return url;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate QR code with:
|
||
|
|
// String url = generateDeepLinkUrl("files");
|
||
|
|
// displayQRCode(url);
|
||
|
|
```
|
||
|
|
|
||
|
|
## App Behavior
|
||
|
|
|
||
|
|
### Launch Scenarios
|
||
|
|
|
||
|
|
#### 1. App Not Running
|
||
|
|
|
||
|
|
When the app is launched via deep link:
|
||
|
|
1. App starts and parses the deep link URL
|
||
|
|
2. Navigates to the target tab
|
||
|
|
3. If device connection info is present and target is "files":
|
||
|
|
- Checks for existing device with matching IP
|
||
|
|
- If found: uses existing device (preserving user's custom name)
|
||
|
|
- If not found: creates temporary connection
|
||
|
|
- Attempts to connect automatically
|
||
|
|
|
||
|
|
#### 2. App Already Running
|
||
|
|
|
||
|
|
When a deep link is received while the app is open:
|
||
|
|
1. `onNewIntent` receives the new URL
|
||
|
|
2. Navigates to the target tab
|
||
|
|
3. Handles device connection (same as above)
|
||
|
|
|
||
|
|
### Device Matching Logic
|
||
|
|
|
||
|
|
When connecting via deep link:
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Look up device by IP address in database
|
||
|
|
2. If device exists:
|
||
|
|
a. Check if ports match
|
||
|
|
b. If ports differ, update the stored device with new ports
|
||
|
|
c. Connect using the existing device (preserves custom name)
|
||
|
|
3. If device doesn't exist:
|
||
|
|
a. Create temporary Device object (not saved to database)
|
||
|
|
b. Connect using temporary device
|
||
|
|
c. Display as "CrossPoint (<ip>)"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Error Handling
|
||
|
|
|
||
|
|
| Scenario | Behavior |
|
||
|
|
|----------|----------|
|
||
|
|
| Malformed URL | App opens to Library tab (default) |
|
||
|
|
| Unknown path | App opens to Library tab with warning logged |
|
||
|
|
| Invalid host format | Navigation succeeds, no auto-connect |
|
||
|
|
| Invalid port values | Default ports used (80, 81) |
|
||
|
|
| Connection failure | Error message displayed, user can retry |
|
||
|
|
| Device unreachable | Error message with device IP shown |
|
||
|
|
|
||
|
|
## Android Implementation Details
|
||
|
|
|
||
|
|
### Intent Filter (AndroidManifest.xml)
|
||
|
|
|
||
|
|
```xml
|
||
|
|
<intent-filter>
|
||
|
|
<action android:name="android.intent.action.VIEW" />
|
||
|
|
<category android:name="android.intent.category.DEFAULT" />
|
||
|
|
<category android:name="android.intent.category.BROWSABLE" />
|
||
|
|
<data android:scheme="crosspoint" />
|
||
|
|
</intent-filter>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Key Classes
|
||
|
|
|
||
|
|
| Class | Purpose |
|
||
|
|
|-------|---------|
|
||
|
|
| `DeepLinkParser` | Parses URI into `DeepLinkData` |
|
||
|
|
| `DeepLinkData` | Data class holding parsed deep link info |
|
||
|
|
| `DeviceConnectionInfo` | Data class for host/port/wsPort |
|
||
|
|
| `MainActivity` | Handles incoming intents |
|
||
|
|
| `CrossPointApp` | Routes navigation based on deep link |
|
||
|
|
| `DeviceBrowserViewModel` | Handles `connectFromDeepLink()` |
|
||
|
|
|
||
|
|
### Data Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
QR Code Scan
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
Android Intent (ACTION_VIEW)
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
MainActivity.onCreate() / onNewIntent()
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
DeepLinkParser.parse(uri)
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
DeepLinkData { targetTab, deviceConnection? }
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
CrossPointApp (LaunchedEffect)
|
||
|
|
│
|
||
|
|
├─► Navigate to targetTab
|
||
|
|
│
|
||
|
|
└─► If targetTab == Device && deviceConnection != null
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
DeviceBrowserScreen
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
DeviceBrowserViewModel.connectFromDeepLink()
|
||
|
|
│
|
||
|
|
├─► Check existing device by IP
|
||
|
|
├─► Update ports if needed
|
||
|
|
└─► Connect and load files
|
||
|
|
```
|
||
|
|
|
||
|
|
## Validation Rules
|
||
|
|
|
||
|
|
### Host Validation
|
||
|
|
|
||
|
|
Valid hosts:
|
||
|
|
- IPv4 addresses: `192.168.1.100`, `10.0.0.1`
|
||
|
|
- Hostnames: `crosspoint.local`, `my-device`
|
||
|
|
|
||
|
|
Invalid hosts (rejected):
|
||
|
|
- Empty strings
|
||
|
|
- Malformed IPs: `192.168.1.256`, `192.168.1`
|
||
|
|
- IPs with invalid octets
|
||
|
|
|
||
|
|
### Port Validation
|
||
|
|
|
||
|
|
- Valid range: 1-65535
|
||
|
|
- Out-of-range values default to 80 (HTTP) or 81 (WebSocket)
|
||
|
|
- Non-numeric values default to standard ports
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
### Manual Testing with ADB
|
||
|
|
|
||
|
|
Test deep links without a QR code using ADB:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Basic navigation
|
||
|
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files"
|
||
|
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://library"
|
||
|
|
|
||
|
|
# With device connection
|
||
|
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files?host=192.168.1.100"
|
||
|
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files?host=192.168.1.100&port=80&wsPort=81"
|
||
|
|
|
||
|
|
# Test while app is running (onNewIntent)
|
||
|
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://settings"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Test Cases
|
||
|
|
|
||
|
|
1. **Valid deep link with connection info**
|
||
|
|
- URL: `crosspoint://files?host=192.168.1.100&port=80&wsPort=81`
|
||
|
|
- Expected: Opens Device tab, auto-connects to device
|
||
|
|
|
||
|
|
2. **Valid deep link without connection info**
|
||
|
|
- URL: `crosspoint://files`
|
||
|
|
- Expected: Opens Device tab, shows device selection
|
||
|
|
|
||
|
|
3. **Unknown path**
|
||
|
|
- URL: `crosspoint://unknown`
|
||
|
|
- Expected: Opens Library tab (default)
|
||
|
|
|
||
|
|
4. **Missing host parameter**
|
||
|
|
- URL: `crosspoint://files?port=80`
|
||
|
|
- Expected: Opens Device tab, no auto-connect
|
||
|
|
|
||
|
|
5. **Invalid host format**
|
||
|
|
- URL: `crosspoint://files?host=invalid..host`
|
||
|
|
- Expected: Opens Device tab, no auto-connect
|
||
|
|
|
||
|
|
6. **Device already exists in database**
|
||
|
|
- Precondition: Device with IP 192.168.1.100 saved as "My Reader"
|
||
|
|
- URL: `crosspoint://files?host=192.168.1.100`
|
||
|
|
- Expected: Connects using "My Reader" name
|
||
|
|
|
||
|
|
7. **Existing device with different ports**
|
||
|
|
- Precondition: Device saved with port=80, wsPort=81
|
||
|
|
- URL: `crosspoint://files?host=192.168.1.100&port=8080&wsPort=8081`
|
||
|
|
- Expected: Updates device ports, then connects
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
1. **Local Network Only**: Deep links should only contain local network addresses. The app does not validate this, but firmware should only generate URLs with local IPs.
|
||
|
|
|
||
|
|
2. **No Authentication**: The deep link does not include authentication. Device security relies on network-level access control.
|
||
|
|
|
||
|
|
3. **Temporary Devices**: Devices created from deep links (when no matching device exists) are not persisted, preventing automatic accumulation of device entries.
|
||
|
|
|
||
|
|
4. **No Sensitive Data**: Deep link URLs should not contain sensitive information as QR codes can be photographed.
|
||
|
|
|
||
|
|
## Changelog
|
||
|
|
|
||
|
|
| Version | Changes |
|
||
|
|
|---------|---------|
|
||
|
|
| 1.0.0 | Initial deep link support with `crosspoint://` scheme |
|