Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement the webview sandbox using a microfront end for rn to implement the full html renderer #2691

Merged
merged 17 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/mobile/native/example/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { SharedWebView } from "follow-native"
import { ScrollView, View } from "react-native"

import { SharedWebView } from "@/src/components/native/webview"

export default function App() {
return (
<ScrollView>
<SharedWebView url="https://innei.in" />

<View style={{ height: 1000, backgroundColor: "red" }} />

Check warning on line 10 in apps/mobile/native/example/App.tsx

View workflow job for this annotation

GitHub Actions / auto-fix

Inline style: { height: 1000, backgroundColor: 'red' }

Check warning on line 10 in apps/mobile/native/example/App.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Inline style: { height: 1000, backgroundColor: 'red' }
</ScrollView>
)
}
2 changes: 1 addition & 1 deletion apps/mobile/native/expo-module.config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"platforms": ["apple", "android"],
"apple": {
"modules": ["SharedWebViewModule"]
"modules": ["SharedWebViewModule", "HelperModule"]
},
"android": {
"modules": []
Expand Down
58 changes: 0 additions & 58 deletions apps/mobile/native/ios/Expo+AutoSizingStack.swift

This file was deleted.

10 changes: 8 additions & 2 deletions apps/mobile/native/ios/FollowNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Pod::Spec.new do |s|
:tvos => '15.1'
}
s.swift_version = '5.4'
s.source = { git: 'https://github.com/Innei/follow-native' }
s.source = { git: 'https://github.com/RSSNext/follow' }
s.static_framework = true

s.dependency 'ExpoModulesCore'
Expand All @@ -25,5 +25,11 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES',
}

s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp,js}"

s.resource_bundles = {
'js' => ['SharedWebView/injected/**/*'],
'FollowNative' => ['Media.xcassets'],
}

end
24 changes: 24 additions & 0 deletions apps/mobile/native/ios/Helper/HelperModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// HelperModule.swift
// Pods
//
// Created by Innei on 2025/2/7.
//
import ExpoModulesCore

public class HelperModule: Module {
public func definition() -> ExpoModulesCore.ModuleDefinition {
Name("Helper")

Function("openLink") { (urlString: String) in
guard let url = URL(string: urlString) else {
return
}
DispatchQueue.main.async {
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return }
WebViewManager.presentModalWebView(url: url, from: rootVC)
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors": [
{
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0x00",
"green": "0x5C",
"red": "0xFF"
}
},
"idiom": "universal"
},
{
"appearances": [
{
"appearance": "luminosity",
"value": "dark"
}
],
"color": {
"color-space": "srgb",
"components": {
"alpha": "1.000",
"blue": "0x00",
"green": "0x5C",
"red": "0xFF"
}
},
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}
6 changes: 6 additions & 0 deletions apps/mobile/native/ios/Media.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}
90 changes: 90 additions & 0 deletions apps/mobile/native/ios/SharedWebView/CustomURLSchemeHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// CustomURLSchemeHandler.swift
// Pods
//
// Created by Innei on 2025/2/7.
//

import WebKit
import Foundation


class CustomURLSchemeHandler: NSObject, WKURLSchemeHandler {
static let rewriteScheme = "follow-xhr"
private let queue = DispatchQueue(label: "com.follow.urlschemehandler")
private var activeTasks: [String: URLSessionDataTask] = [:]

func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url,
let originalURLString = url.absoluteString.replacingOccurrences(
of: CustomURLSchemeHandler.rewriteScheme, with: "https"
).removingPercentEncoding,
let originalURL = URL(string: originalURLString)
else {
urlSchemeTask.didFailWithError(NSError(domain: "", code: -1))
return
}

var request = URLRequest(url: originalURL)

request.httpMethod = urlSchemeTask.request.httpMethod
request.httpBody = urlSchemeTask.request.httpBody

// setting headers
var headers = urlSchemeTask.request.allHTTPHeaderFields ?? [:]
if let urlComponents = URLComponents(url: originalURL, resolvingAgainstBaseURL: false),
let scheme = urlComponents.scheme,
let host = urlComponents.host
{
let origin = "\(scheme)://\(host)\(urlComponents.port.map { ":\($0)" } ?? "")"
headers["Referer"] = origin
headers["Origin"] = origin

}
request.allHTTPHeaderFields = headers

let taskID = urlSchemeTask.description

let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard let self = self else { return }

self.queue.sync {
// Check if task is still active
guard self.activeTasks[taskID] != nil else { return }

if let error = error {
urlSchemeTask.didFailWithError(error)
self.activeTasks.removeValue(forKey: taskID)
return
}

if let response = response as? HTTPURLResponse, let data = data {
do {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
} catch {
// Ignore errors that might occur if task was stopped
print("Error completing URL scheme task: \(error)")
}
}
self.activeTasks.removeValue(forKey: taskID)
}
}
queue.sync {
activeTasks[taskID] = task
}

task.resume()
}

func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
let taskID = urlSchemeTask.description
queue.sync {
if let task = activeTasks[taskID] {
task.cancel()
activeTasks.removeValue(forKey: taskID)
}
}
}
}
35 changes: 35 additions & 0 deletions apps/mobile/native/ios/SharedWebView/FOWebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// FOWebView.swift
// FollowNative
//
// Created by Innei on 2025/2/7.
//

import WebKit

class FOWebView: WKWebView {
private func setupView() {
scrollView.isScrollEnabled = false
scrollView.bounces = false
scrollView.contentInsetAdjustmentBehavior = .never

isOpaque = false
backgroundColor = UIColor.clear
scrollView.backgroundColor = UIColor.clear
tintColor = Utils.accentColor

if #available(iOS 16.4, *) {
isInspectable = true
}

}

override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
setupView()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
25 changes: 25 additions & 0 deletions apps/mobile/native/ios/SharedWebView/Injected/at_end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// at_end.js
// Pods
//
// Created by Innei on 2025/2/6.
//

;(() => {
const root = document.querySelector("#root")
const handleHeight = () => {
window.webkit.messageHandlers.message.postMessage(
JSON.stringify({
type: "setContentHeight",
payload: root.scrollHeight,
}),
)
}
window.addEventListener("load", handleHeight)
const observer = new ResizeObserver(handleHeight)

setTimeout(() => {
handleHeight()
}, 1000)
observer.observe(root)
})()
27 changes: 27 additions & 0 deletions apps/mobile/native/ios/SharedWebView/Injected/at_start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// at_start.js
// Pods
//
// Created by Innei on 2025/2/6.
//
;(() => {
window.__RN__ = true

function send(data) {
window.webkit.messageHandlers.message.postMessage?.(JSON.stringify(data))
}

Object.assign(window.webkit, {
measure: () => {
send({
type: "measure",
})
},
setContentHeight: (height) => {
send({
type: "setContentHeight",
payload: height,
})
},
})
})()
Loading
Loading