Skip to content

Commit 6029f47

Browse files
committed
Add platform documentation for Android and iOS
Add dev guides for Android and iOS native integration. Update README with native package listing, platform guides, and architecture notes.
1 parent 984361a commit 6029f47

3 files changed

Lines changed: 331 additions & 8 deletions

File tree

README.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,33 @@ Uses native property setters to bypass React/Vue/Angular framework interceptors.
8787

8888
## Packages
8989

90-
| Package | Description |
91-
|---------|-------------|
92-
| `@snapfill/core` | Core detection + filling engine |
93-
| `@snapfill/react-native` | React Native WebView adapter (hook + component) |
90+
| Package | Description | Platform |
91+
|---------|-------------|----------|
92+
| `@snapfill/core` | Core detection + filling engine | Any JS runtime |
93+
| `@snapfill/react-native` | React Native WebView adapter (hook + component) | React Native |
94+
| `packages/android` | Kotlin library for Android `WebView` | Android 7.0+ |
95+
| `packages/ios` | Swift Package for `WKWebView` | iOS 15+ |
96+
97+
## Platform Guides
98+
99+
- [React Native](docs/react-native-dev.md) — hook, component, and Expo demo setup
100+
- [Android](docs/android-dev.md) — Kotlin `Snapfill` helper and `SnapfillWebView`
101+
- [iOS](docs/ios-dev.md) — Swift `Snapfill` helper and `SnapfillWebView`
94102

95103
## Development
96104

97105
```bash
98-
pnpm install # Install dependencies
99-
pnpm build # Build all packages
100-
pnpm test # Run tests
101-
pnpm lint # Lint code
106+
pnpm install # Install dependencies
107+
pnpm build # Build all packages
108+
pnpm test # Run tests
109+
pnpm lint # Lint code
110+
pnpm generate:native # Generate JS assets for Android & iOS
102111
```
103112

113+
### Native Libraries
114+
115+
The Android and iOS libraries load the same injectable scripts from `@snapfill/core`. After building core, run `pnpm generate:native` to copy the JS assets into both native packages. See the platform guides above for build and test instructions.
116+
104117
## Architecture
105118

106119
```
@@ -114,8 +127,13 @@ pnpm lint # Lint code
114127
├── injectable # WebView-injectable script strings
115128
├── constants # Regex patterns, autocomplete maps
116129
└── types # TypeScript type definitions
130+
131+
packages/android # Kotlin library — Snapfill, SnapfillWebView, SnapfillListener
132+
packages/ios # Swift Package — Snapfill, SnapfillWebView, SnapfillDelegate
117133
```
118134

119135
Each module exports both:
120136
- **Functions** for direct use in web context (tree-shakeable)
121137
- **Script strings** (via `injectable.ts`) for WebView injection
138+
139+
Native libraries use the script strings, injecting them via platform-specific WebView APIs (`evaluateJavascript` on Android, `WKUserScript` on iOS). A bridge shim creates `window.ReactNativeWebView.postMessage()` so the same scripts work across all platforms.

docs/android-dev.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Android Development Guide
2+
3+
## Architecture Overview
4+
5+
```
6+
packages/android/ Kotlin library (AAR)
7+
↓ loads at runtime
8+
packages/android/src/main/assets/ snapfill.js + snapfill-fill.js (generated from @snapfill/core)
9+
```
10+
11+
The Android library loads the same injectable scripts that `@snapfill/react-native` uses. It creates a `window.ReactNativeWebView` shim that routes `postMessage` calls to a native `@JavascriptInterface` bridge, so the scripts work unchanged.
12+
13+
## Setup
14+
15+
### Generate JavaScript Assets
16+
17+
The JS assets must be generated from `@snapfill/core` before building:
18+
19+
```bash
20+
pnpm install
21+
pnpm build # Build @snapfill/core
22+
pnpm generate:native # Generates snapfill.js + snapfill-fill.js into assets/
23+
```
24+
25+
### Gradle Integration
26+
27+
Add the library as a module in your Android project's `settings.gradle.kts`:
28+
29+
```kotlin
30+
include(":snapfill")
31+
project(":snapfill").projectDir = file("path/to/packages/android")
32+
```
33+
34+
Then add the dependency:
35+
36+
```kotlin
37+
dependencies {
38+
implementation(project(":snapfill"))
39+
}
40+
```
41+
42+
## Usage
43+
44+
### Option A: Helper class (attach to any WebView)
45+
46+
```kotlin
47+
val webView = findViewById<WebView>(R.id.webview)
48+
val snapfill = Snapfill(webView)
49+
50+
snapfill.listener = object : SnapfillListener {
51+
override fun onFormDetected(fields: List<String>) {
52+
Log.d("Snapfill", "Detected fields: $fields")
53+
}
54+
55+
override fun onCartDetected(cart: SnapfillCart) {
56+
Log.d("Snapfill", "Cart total: ${cart.total} ${cart.currency}")
57+
}
58+
59+
override fun onValuesCaptured(mappings: Map<String, String>) {
60+
Log.d("Snapfill", "Captured: $mappings")
61+
}
62+
63+
override fun onFormFillComplete(result: SnapfillFillResult) {
64+
Log.d("Snapfill", "Filled ${result.filled}/${result.total}")
65+
}
66+
}
67+
68+
snapfill.attach()
69+
webView.loadUrl("https://example.com/checkout")
70+
71+
// Fill a form
72+
snapfill.fillForm(mapOf(
73+
"firstName" to "John",
74+
"lastName" to "Doe",
75+
"email" to "john@example.com"
76+
))
77+
```
78+
79+
### Option B: SnapfillWebView subclass
80+
81+
```kotlin
82+
val webView = SnapfillWebView(context)
83+
84+
webView.snapfillListener = object : SnapfillListener {
85+
override fun onFormDetected(fields: List<String>) { ... }
86+
}
87+
88+
webView.loadUrl("https://example.com/checkout")
89+
webView.fillForm(mapOf("email" to "john@example.com"))
90+
```
91+
92+
## Bridge Mechanism
93+
94+
1. **`@JavascriptInterface`**`webView.addJavascriptInterface(bridge, "SnapfillBridge")` exposes a native `onMessage(msg)` method to JavaScript
95+
2. **Bridge shim** — injected at page start: `window.ReactNativeWebView={postMessage:function(m){SnapfillBridge.onMessage(m);}};`
96+
3. **Detection scripts** — injected via `WebViewClient.onPageFinished()` using `evaluateJavascript()`
97+
4. **Messages** — parsed from JSON and dispatched to `SnapfillListener` on the main thread
98+
99+
## API Reference
100+
101+
### `Snapfill`
102+
103+
| Method | Description |
104+
|---|---|
105+
| `attach()` | Sets up the JavaScript bridge and WebViewClient |
106+
| `fillForm(mappings)` | Injects a fill script with the given field mappings |
107+
| `reinject()` | Re-injects bridge shim and detection scripts |
108+
| `detach()` | Removes the JavaScript bridge |
109+
110+
### `SnapfillListener`
111+
112+
All methods have default no-op implementations.
113+
114+
| Callback | Trigger |
115+
|---|---|
116+
| `onFormDetected(fields)` | Form fields detected on the page |
117+
| `onCartDetected(cart)` | Shopping cart data extracted |
118+
| `onValuesCaptured(mappings)` | User-entered values captured |
119+
| `onFormFillComplete(result)` | Form fill operation completed |
120+
121+
### `SnapfillOptions`
122+
123+
| Field | Default | Description |
124+
|---|---|---|
125+
| `detectForms` | `true` | Enable form field detection |
126+
| `detectCart` | `true` | Enable cart detection |
127+
| `captureValues` | `true` | Enable value capture |
128+
129+
## Running Tests
130+
131+
```bash
132+
cd packages/android
133+
./gradlew test
134+
```
135+
136+
## Key Design Decisions
137+
138+
**No external dependencies**: The library uses only `android.webkit.WebView` and `org.json` (both part of the Android SDK).
139+
140+
**Main thread dispatch**: All `SnapfillListener` callbacks are dispatched on the main thread via `Handler(Looper.getMainLooper())`.
141+
142+
**Min SDK 24**: Android 7.0+ is required for `WebView.evaluateJavascript()` stability and consistent `@JavascriptInterface` behavior.

docs/ios-dev.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# iOS Development Guide
2+
3+
## Architecture Overview
4+
5+
```
6+
packages/ios/ Swift Package
7+
↓ loads at runtime
8+
packages/ios/Sources/Snapfill/Resources/ snapfill.js + snapfill-fill.js (generated from @snapfill/core)
9+
```
10+
11+
The iOS library loads the same injectable scripts that `@snapfill/react-native` uses. It creates a `window.ReactNativeWebView` shim that routes `postMessage` calls to a `WKScriptMessageHandler`, so the scripts work unchanged.
12+
13+
## Setup
14+
15+
### Generate JavaScript Assets
16+
17+
The JS assets must be generated from `@snapfill/core` before building:
18+
19+
```bash
20+
pnpm install
21+
pnpm build # Build @snapfill/core
22+
pnpm generate:native # Generates snapfill.js + snapfill-fill.js into Resources/
23+
```
24+
25+
### Swift Package Manager
26+
27+
Add the package as a local dependency in Xcode or in your `Package.swift`:
28+
29+
```swift
30+
.package(path: "path/to/packages/ios")
31+
```
32+
33+
Then add `Snapfill` to your target's dependencies.
34+
35+
## Usage
36+
37+
### Option A: Helper class (attach to any WKWebView)
38+
39+
```swift
40+
import Snapfill
41+
import WebKit
42+
43+
class CheckoutViewController: UIViewController, SnapfillDelegate {
44+
let webView = WKWebView()
45+
var snapfill: Snapfill!
46+
47+
override func viewDidLoad() {
48+
super.viewDidLoad()
49+
50+
snapfill = Snapfill(webView: webView)
51+
snapfill.delegate = self
52+
snapfill.attach()
53+
54+
webView.load(URLRequest(url: URL(string: "https://example.com/checkout")!))
55+
}
56+
57+
// MARK: - SnapfillDelegate
58+
59+
func snapfillDidDetectFields(_ snapfill: Snapfill, fields: [String]) {
60+
print("Detected fields: \(fields)")
61+
}
62+
63+
func snapfillDidDetectCart(_ snapfill: Snapfill, cart: SnapfillCart) {
64+
print("Cart total: \(cart.total) \(cart.currency ?? "")")
65+
}
66+
67+
func snapfillDidCaptureValues(_ snapfill: Snapfill, mappings: [String: String]) {
68+
print("Captured: \(mappings)")
69+
}
70+
71+
func snapfillDidCompleteFill(_ snapfill: Snapfill, result: SnapfillFillResult) {
72+
print("Filled \(result.filled)/\(result.total)")
73+
}
74+
75+
func fillCheckout() {
76+
snapfill.fillForm([
77+
"firstName": "John",
78+
"lastName": "Doe",
79+
"email": "john@example.com"
80+
])
81+
}
82+
}
83+
```
84+
85+
### Option B: SnapfillWebView subclass
86+
87+
```swift
88+
import Snapfill
89+
90+
class CheckoutViewController: UIViewController, SnapfillDelegate {
91+
let webView = SnapfillWebView()
92+
93+
override func viewDidLoad() {
94+
super.viewDidLoad()
95+
webView.snapfillDelegate = self
96+
webView.load(URLRequest(url: URL(string: "https://example.com/checkout")!))
97+
}
98+
99+
func snapfillDidDetectFields(_ snapfill: Snapfill, fields: [String]) { ... }
100+
101+
func fillCheckout() {
102+
webView.fillForm(["email": "john@example.com"])
103+
}
104+
}
105+
```
106+
107+
## Bridge Mechanism
108+
109+
1. **`WKScriptMessageHandler`**`userContentController.add(self, name: "snapfill")` registers a message handler
110+
2. **Bridge shim** — injected as `WKUserScript` at `.atDocumentStart`: `window.ReactNativeWebView={postMessage:function(m){webkit.messageHandlers.snapfill.postMessage(m);}};`
111+
3. **Detection scripts** — injected as `WKUserScript` at `.atDocumentEnd`
112+
4. **Messages** — parsed from JSON and dispatched to `SnapfillDelegate` on the main thread
113+
5. **Fill**`webView.evaluateJavaScript()` injects the fill script with mappings
114+
115+
## API Reference
116+
117+
### `Snapfill`
118+
119+
| Method | Description |
120+
|---|---|
121+
| `attach()` | Adds user scripts and message handler to the WKWebView |
122+
| `fillForm(_:)` | Injects a fill script with the given field mappings |
123+
| `reinject()` | Re-injects bridge shim and detection scripts |
124+
| `detach()` | Removes all user scripts and the message handler |
125+
126+
### `SnapfillDelegate`
127+
128+
All methods are optional via default extensions.
129+
130+
| Callback | Trigger |
131+
|---|---|
132+
| `snapfillDidDetectFields(_:fields:)` | Form fields detected on the page |
133+
| `snapfillDidDetectCart(_:cart:)` | Shopping cart data extracted |
134+
| `snapfillDidCaptureValues(_:mappings:)` | User-entered values captured |
135+
| `snapfillDidCompleteFill(_:result:)` | Form fill operation completed |
136+
137+
### `SnapfillOptions`
138+
139+
| Field | Default | Description |
140+
|---|---|---|
141+
| `detectForms` | `true` | Enable form field detection |
142+
| `detectCart` | `true` | Enable cart detection |
143+
| `captureValues` | `true` | Enable value capture |
144+
145+
## Running Tests
146+
147+
```bash
148+
cd packages/ios
149+
swift build
150+
swift test
151+
```
152+
153+
## Key Design Decisions
154+
155+
**No external dependencies**: The library uses only the `WebKit` framework (part of iOS SDK).
156+
157+
**Main thread dispatch**: All `SnapfillDelegate` callbacks are dispatched on the main thread via `DispatchQueue.main.async`.
158+
159+
**`Bundle.module` for resources**: Swift Package Manager's resource processing creates a `Bundle.module` accessor for the generated JS files.
160+
161+
**Min iOS 15**: Required for stable `WKUserScript` injection timing and `async/await` availability in the platform.
162+
163+
**Sendable conformance**: All model structs conform to `Sendable` for safe use with Swift concurrency.

0 commit comments

Comments
 (0)