Skip to content

Commit 399d6dc

Browse files
ardaerturkclaude
andauthored
feat: add Web3 integration and enhance browser functionality (#78)
- Add web3-injection.js for Web3 provider injection into webpages - Implement LiquidWebView for Web3-enabled browsing - Add NativeWebBrowserView with enhanced browser features - Create EnhancedSafariNavigationBar for improved navigation - Update Web3MessageHandler for wallet interactions - Enhance WebBrowserView with Web3 capabilities - Improve WebNavigationBar and WebViewTransition - Add WebPage model for better state management - Remove deprecated WebSearchOverlay component This enables Web3 dApp interactions directly within the app with seamless wallet integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: ardaerturk <ardaerturk@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6923971 commit 399d6dc

11 files changed

Lines changed: 2118 additions & 617 deletions

Interspace/Models/WebPage.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import Foundation
2+
import Combine
3+
import WebKit
4+
5+
// MARK: - WebPage
6+
/// Observable model for managing web page state and navigation
7+
/// Implements iOS 26 WebPage pattern for SwiftUI integration
8+
class WebPage: ObservableObject {
9+
// MARK: - Published Properties
10+
11+
@Published var url: URL?
12+
@Published var title: String = ""
13+
@Published var isLoading: Bool = false
14+
@Published var estimatedProgress: Double = 0.0
15+
@Published var canGoBack: Bool = false
16+
@Published var canGoForward: Bool = false
17+
@Published var error: Error?
18+
19+
// MARK: - Navigation Actions
20+
21+
var onLoadRequest: ((URLRequest) -> Void)?
22+
var onLoadURL: ((URL) -> Void)?
23+
var onGoBack: (() -> Void)?
24+
var onGoForward: (() -> Void)?
25+
var onReload: (() -> Void)?
26+
var onStopLoading: (() -> Void)?
27+
var onEvaluateJavaScript: ((String, @escaping (Any?, Error?) -> Void) -> Void)?
28+
29+
// MARK: - Initialization
30+
31+
init(url: URL? = nil) {
32+
self.url = url
33+
}
34+
35+
// MARK: - Public Methods
36+
37+
/// Load a URL
38+
func load(_ url: URL) {
39+
self.url = url
40+
onLoadURL?(url)
41+
}
42+
43+
/// Load a URL request
44+
func load(_ request: URLRequest) {
45+
self.url = request.url
46+
onLoadRequest?(request)
47+
}
48+
49+
/// Navigate back
50+
func goBack() {
51+
onGoBack?()
52+
}
53+
54+
/// Navigate forward
55+
func goForward() {
56+
onGoForward?()
57+
}
58+
59+
/// Reload the current page
60+
func reload() {
61+
onReload?()
62+
}
63+
64+
/// Stop loading the current page
65+
func stopLoading() {
66+
onStopLoading?()
67+
}
68+
69+
/// Execute JavaScript on the page
70+
func evaluateJavaScript(_ script: String, completion: @escaping (Any?, Error?) -> Void) {
71+
onEvaluateJavaScript?(script, completion)
72+
}
73+
74+
/// Reset error state
75+
func clearError() {
76+
error = nil
77+
}
78+
}
79+
80+
// MARK: - WebPage Extensions for iOS 26
81+
extension WebPage {
82+
/// Create a web page for a bookmarked app
83+
static func from(app: BookmarkedApp) -> WebPage {
84+
guard let appURL = URL(string: app.url) else {
85+
return WebPage()
86+
}
87+
return WebPage(url: appURL)
88+
}
89+
90+
/// Check if the page is secure (HTTPS)
91+
var isSecure: Bool {
92+
url?.scheme == "https"
93+
}
94+
95+
/// Get the host name for display
96+
var displayHost: String {
97+
url?.host ?? ""
98+
}
99+
100+
/// Get a short display title
101+
var displayTitle: String {
102+
if !title.isEmpty {
103+
return title
104+
} else if let host = url?.host {
105+
return host
106+
} else {
107+
return "New Page"
108+
}
109+
}
110+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Interspace Web3 Provider Injection Script
2+
(function() {
3+
'use strict';
4+
5+
// Check if already injected
6+
if (window.ethereum && window.ethereum.isInterspace) {
7+
console.log('Interspace Web3 provider already injected');
8+
return;
9+
}
10+
11+
// Create message handler
12+
function sendMessage(type, data) {
13+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.interspaceWeb3) {
14+
window.webkit.messageHandlers.interspaceWeb3.postMessage({
15+
type: type,
16+
...data
17+
});
18+
}
19+
}
20+
21+
// Response handler
22+
let responseHandlers = {};
23+
let requestId = 0;
24+
25+
window.__handleInterspaceMessage = function(response) {
26+
if (response.type === 'web3_response' && responseHandlers[response.id]) {
27+
const handler = responseHandlers[response.id];
28+
delete responseHandlers[response.id];
29+
30+
if (response.error) {
31+
handler.reject(new Error(response.error.message || 'Unknown error'));
32+
} else {
33+
handler.resolve(response.result);
34+
}
35+
} else if (response.type === 'initial_state') {
36+
// Handle initial state
37+
if (response.result) {
38+
ethereum._chainId = response.result.chainId;
39+
ethereum._accounts = response.result.accounts || [];
40+
ethereum._isConnected = response.result.isConnected || false;
41+
42+
// Emit connect event if connected
43+
if (ethereum._isConnected && ethereum._accounts.length > 0) {
44+
ethereum.emit('connect', { chainId: ethereum._chainId });
45+
ethereum.emit('accountsChanged', ethereum._accounts);
46+
}
47+
}
48+
} else if (response.type === 'web3_event') {
49+
// Handle events
50+
ethereum.emit(response.event, response.data);
51+
}
52+
};
53+
54+
// Event emitter
55+
class EventEmitter {
56+
constructor() {
57+
this.events = {};
58+
}
59+
60+
on(event, callback) {
61+
if (!this.events[event]) {
62+
this.events[event] = [];
63+
}
64+
this.events[event].push(callback);
65+
return this;
66+
}
67+
68+
once(event, callback) {
69+
const onceWrapper = (...args) => {
70+
callback(...args);
71+
this.removeListener(event, onceWrapper);
72+
};
73+
return this.on(event, onceWrapper);
74+
}
75+
76+
removeListener(event, callback) {
77+
if (this.events[event]) {
78+
this.events[event] = this.events[event].filter(cb => cb !== callback);
79+
}
80+
return this;
81+
}
82+
83+
emit(event, ...args) {
84+
if (this.events[event]) {
85+
this.events[event].forEach(callback => {
86+
try {
87+
callback(...args);
88+
} catch (error) {
89+
console.error('Event handler error:', error);
90+
}
91+
});
92+
}
93+
}
94+
}
95+
96+
// Ethereum provider
97+
class InterspaceProvider extends EventEmitter {
98+
constructor() {
99+
super();
100+
this.isInterspace = true;
101+
this.isMetaMask = true; // For compatibility
102+
this._chainId = '0x1';
103+
this._accounts = [];
104+
this._isConnected = false;
105+
106+
// Request initial state
107+
sendMessage('get_initial_state', {});
108+
}
109+
110+
isConnected() {
111+
return this._isConnected;
112+
}
113+
114+
async request(args) {
115+
if (!args || typeof args !== 'object' || typeof args.method !== 'string') {
116+
throw new Error('Invalid request');
117+
}
118+
119+
const id = ++requestId;
120+
121+
return new Promise((resolve, reject) => {
122+
responseHandlers[id] = { resolve, reject };
123+
124+
// Send request to native
125+
sendMessage('web3_request', {
126+
id: id,
127+
method: args.method,
128+
params: args.params || []
129+
});
130+
131+
// Timeout after 60 seconds
132+
setTimeout(() => {
133+
if (responseHandlers[id]) {
134+
delete responseHandlers[id];
135+
reject(new Error('Request timeout'));
136+
}
137+
}, 60000);
138+
});
139+
}
140+
141+
// Legacy methods for compatibility
142+
async enable() {
143+
return this.request({ method: 'eth_requestAccounts' });
144+
}
145+
146+
async send(method, params = []) {
147+
return this.request({ method, params });
148+
}
149+
150+
sendAsync(payload, callback) {
151+
this.request({
152+
method: payload.method,
153+
params: payload.params
154+
}).then(result => {
155+
callback(null, {
156+
id: payload.id,
157+
jsonrpc: '2.0',
158+
result
159+
});
160+
}).catch(error => {
161+
callback(error, null);
162+
});
163+
}
164+
}
165+
166+
// Create and inject provider
167+
const ethereum = new InterspaceProvider();
168+
169+
// Define as non-configurable to prevent overwriting
170+
Object.defineProperty(window, 'ethereum', {
171+
value: ethereum,
172+
writable: false,
173+
configurable: false
174+
});
175+
176+
// For compatibility with older dApps
177+
window.web3 = {
178+
currentProvider: ethereum,
179+
eth: {
180+
accounts: ethereum._accounts
181+
}
182+
};
183+
184+
// Notify that injection is complete
185+
sendMessage('injection_complete', {
186+
url: window.location.href
187+
});
188+
189+
console.log('✅ Interspace Web3 provider injected successfully');
190+
191+
// Dispatch ethereum provider event
192+
window.dispatchEvent(new Event('ethereum#initialized'));
193+
})();

0 commit comments

Comments
 (0)