Skip to content

Commit 5528862

Browse files
committed
feat: add ios webview implementation
1 parent 00bb772 commit 5528862

File tree

2 files changed

+346
-0
lines changed

2 files changed

+346
-0
lines changed
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEngine.UI;
4+
using Immutable.Passport.Core.Logging;
5+
6+
#if UNITY_IOS && !UNITY_EDITOR
7+
using Immutable.Browser.Gree;
8+
#endif
9+
10+
namespace Immutable.Passport
11+
{
12+
#if UNITY_IOS && !UNITY_EDITOR
13+
/// <summary>
14+
/// iOS implementation of IPassportWebView using Gree WebView (WKWebView)
15+
/// Wraps Gree WebViewObject in a clean, platform-agnostic interface
16+
/// </summary>
17+
public class iOSPassportWebView : IPassportWebView
18+
{
19+
private const string TAG = "[iOSPassportWebView]";
20+
21+
// Gree WebView components
22+
private WebViewObject webViewObject;
23+
private GameObject webViewGameObject;
24+
25+
// Configuration and state
26+
private RawImage targetRawImage;
27+
private MonoBehaviour coroutineRunner;
28+
private PassportWebViewConfig config;
29+
private bool isInitialized = false;
30+
private bool isVisible = false;
31+
private string currentUrl = "";
32+
33+
// Events
34+
public event Action<string> OnJavaScriptMessage;
35+
public event Action OnLoadFinished;
36+
public event Action OnLoadStarted;
37+
38+
// Properties
39+
public bool IsVisible => isVisible;
40+
public string CurrentUrl => currentUrl;
41+
42+
/// <summary>
43+
/// Constructor for iOS PassportWebView
44+
/// </summary>
45+
/// <param name="targetRawImage">RawImage component to display WebView content (not used on iOS - native overlay)</param>
46+
/// <param name="coroutineRunner">MonoBehaviour to run coroutines (for consistency with other platforms)</param>
47+
public iOSPassportWebView(RawImage targetRawImage, MonoBehaviour coroutineRunner)
48+
{
49+
this.targetRawImage = targetRawImage ?? throw new ArgumentNullException(nameof(targetRawImage));
50+
this.coroutineRunner = coroutineRunner ?? throw new ArgumentNullException(nameof(coroutineRunner));
51+
52+
PassportLogger.Info($"{TAG} iOS WebView wrapper created");
53+
}
54+
55+
public void Initialize(PassportWebViewConfig config)
56+
{
57+
if (isInitialized)
58+
{
59+
PassportLogger.Warn($"{TAG} Already initialized, skipping");
60+
return;
61+
}
62+
63+
this.config = config ?? new PassportWebViewConfig();
64+
65+
try
66+
{
67+
PassportLogger.Info($"{TAG} Initializing iOS WebView with Gree WebView...");
68+
69+
CreateWebViewObject();
70+
ConfigureWebView();
71+
72+
isInitialized = true;
73+
PassportLogger.Info($"{TAG} iOS WebView initialized successfully");
74+
}
75+
catch (Exception ex)
76+
{
77+
PassportLogger.Error($"{TAG} Failed to initialize: {ex.Message}");
78+
throw;
79+
}
80+
}
81+
82+
public void LoadUrl(string url)
83+
{
84+
if (!isInitialized)
85+
{
86+
PassportLogger.Error($"{TAG} Cannot load URL - WebView not initialized");
87+
return;
88+
}
89+
90+
if (string.IsNullOrEmpty(url))
91+
{
92+
PassportLogger.Error($"{TAG} Cannot load empty URL");
93+
return;
94+
}
95+
96+
try
97+
{
98+
PassportLogger.Info($"{TAG} Loading URL: {url}");
99+
currentUrl = url;
100+
101+
// iOS implementation: Use LaunchAuthURL to open in external browser (Safari/SFSafariViewController)
102+
// This follows the same pattern as the main Passport SDK for iOS authentication
103+
webViewObject.LaunchAuthURL(url, "immutablerunner://callback");
104+
PassportLogger.Info($"{TAG} URL launched in external browser: {url}");
105+
106+
// Trigger load started event
107+
OnLoadStarted?.Invoke();
108+
109+
// For iOS, we consider the load "finished" immediately since it opens externally
110+
OnLoadFinished?.Invoke();
111+
}
112+
catch (Exception ex)
113+
{
114+
PassportLogger.Error($"{TAG} Failed to load URL: {ex.Message}");
115+
}
116+
}
117+
118+
public void Show()
119+
{
120+
if (!isInitialized)
121+
{
122+
PassportLogger.Error($"{TAG} Cannot show - WebView not initialized");
123+
return;
124+
}
125+
126+
try
127+
{
128+
PassportLogger.Info($"{TAG} Showing WebView (iOS uses external browser)");
129+
130+
// iOS implementation: WebView is always "shown" since we use external browser
131+
// The actual display happens when LoadUrl calls LaunchAuthURL
132+
isVisible = true;
133+
134+
PassportLogger.Info($"{TAG} WebView shown successfully");
135+
}
136+
catch (Exception ex)
137+
{
138+
PassportLogger.Error($"{TAG} Failed to show WebView: {ex.Message}");
139+
}
140+
}
141+
142+
public void Hide()
143+
{
144+
if (!isInitialized)
145+
{
146+
PassportLogger.Warn($"{TAG} Cannot hide - WebView not initialized");
147+
return;
148+
}
149+
150+
try
151+
{
152+
PassportLogger.Info($"{TAG} Hiding WebView (iOS external browser will close automatically)");
153+
154+
// iOS implementation: External browser closes automatically after auth
155+
// No explicit hide needed
156+
isVisible = false;
157+
158+
PassportLogger.Info($"{TAG} WebView hidden successfully");
159+
}
160+
catch (Exception ex)
161+
{
162+
PassportLogger.Error($"{TAG} Failed to hide WebView: {ex.Message}");
163+
}
164+
}
165+
166+
public void ExecuteJavaScript(string js)
167+
{
168+
if (!isInitialized)
169+
{
170+
PassportLogger.Error($"{TAG} Cannot execute JavaScript - WebView not initialized");
171+
return;
172+
}
173+
174+
try
175+
{
176+
PassportLogger.Debug($"{TAG} Executing JavaScript: {js.Substring(0, Math.Min(100, js.Length))}...");
177+
webViewObject.EvaluateJS(js);
178+
}
179+
catch (Exception ex)
180+
{
181+
PassportLogger.Error($"{TAG} Failed to execute JavaScript: {ex.Message}");
182+
}
183+
}
184+
185+
public void RegisterJavaScriptMethod(string methodName, Action<string> handler)
186+
{
187+
if (!isInitialized)
188+
{
189+
PassportLogger.Error($"{TAG} Cannot register JavaScript method - WebView not initialized");
190+
return;
191+
}
192+
193+
try
194+
{
195+
PassportLogger.Info($"{TAG} Registering JavaScript method: {methodName}");
196+
197+
// iOS implementation: Since we use external browser (Safari/SFSafariViewController),
198+
// JavaScript methods are handled through deep links and auth callbacks
199+
// The login data will be captured via the auth callback when the external browser
200+
// redirects back to the app with immutablerunner://callback
201+
202+
PassportLogger.Info($"{TAG} JavaScript method '{methodName}' registered (handled via deep link callbacks on iOS)");
203+
}
204+
catch (Exception ex)
205+
{
206+
PassportLogger.Error($"{TAG} Failed to register JavaScript method: {ex.Message}");
207+
}
208+
}
209+
210+
public void Dispose()
211+
{
212+
try
213+
{
214+
PassportLogger.Info($"{TAG} Disposing iOS WebView");
215+
216+
// Destroy WebView GameObject
217+
if (webViewGameObject != null)
218+
{
219+
UnityEngine.Object.DestroyImmediate(webViewGameObject);
220+
webViewGameObject = null;
221+
}
222+
223+
// Clear references
224+
webViewObject = null;
225+
targetRawImage = null;
226+
coroutineRunner = null;
227+
228+
isInitialized = false;
229+
isVisible = false;
230+
231+
PassportLogger.Info($"{TAG} iOS WebView disposed successfully");
232+
}
233+
catch (Exception ex)
234+
{
235+
PassportLogger.Error($"{TAG} Error during disposal: {ex.Message}");
236+
}
237+
}
238+
239+
#region Private Implementation
240+
241+
private void CreateWebViewObject()
242+
{
243+
PassportLogger.Info($"{TAG} Creating Gree WebViewObject...");
244+
245+
// Create GameObject for WebView
246+
webViewGameObject = new GameObject("PassportUI_iOS_WebView");
247+
UnityEngine.Object.DontDestroyOnLoad(webViewGameObject);
248+
249+
// Add WebViewObject component
250+
webViewObject = webViewGameObject.AddComponent<WebViewObject>();
251+
252+
PassportLogger.Info($"{TAG} Gree WebViewObject created successfully");
253+
}
254+
255+
private void ConfigureWebView()
256+
{
257+
PassportLogger.Info($"{TAG} Configuring Gree WebView...");
258+
259+
// Initialize WebViewObject with callbacks
260+
webViewObject.Init(
261+
cb: OnWebViewMessage,
262+
httpErr: OnWebViewHttpError,
263+
err: OnWebViewError,
264+
auth: OnWebViewAuth,
265+
log: OnWebViewLog
266+
);
267+
268+
// Configure WebView settings
269+
webViewObject.SetMargins(0, 0, 0, 0); // Full screen
270+
webViewObject.SetVisibility(false); // Start hidden
271+
272+
// Clear cache if requested
273+
if (config.ClearCacheOnInit)
274+
{
275+
webViewObject.ClearCache(true);
276+
PassportLogger.Info($"{TAG} WebView cache cleared");
277+
}
278+
279+
PassportLogger.Info($"{TAG} Gree WebView configured successfully");
280+
}
281+
282+
#endregion
283+
284+
#region WebView Event Handlers
285+
286+
private void OnWebViewMessage(string message)
287+
{
288+
try
289+
{
290+
PassportLogger.Debug($"{TAG} WebView message: {message}");
291+
292+
// Parse message to see if it's a JavaScript method call
293+
if (message.Contains("method") && message.Contains("data"))
294+
{
295+
// This is a JavaScript method call from our registered methods
296+
OnJavaScriptMessage?.Invoke(message);
297+
}
298+
else
299+
{
300+
// Regular WebView message
301+
OnJavaScriptMessage?.Invoke(message);
302+
}
303+
}
304+
catch (Exception ex)
305+
{
306+
PassportLogger.Error($"{TAG} Error handling WebView message: {ex.Message}");
307+
}
308+
}
309+
310+
private void OnWebViewHttpError(string id, string message)
311+
{
312+
PassportLogger.Error($"{TAG} WebView HTTP error [{id}]: {message}");
313+
}
314+
315+
private void OnWebViewError(string id, string message)
316+
{
317+
PassportLogger.Error($"{TAG} WebView error [{id}]: {message}");
318+
}
319+
320+
private void OnWebViewAuth(string message)
321+
{
322+
PassportLogger.Info($"{TAG} WebView auth message: {message}");
323+
// Auth messages could be login completion, etc.
324+
OnJavaScriptMessage?.Invoke(message);
325+
}
326+
327+
private void OnWebViewLog(string message)
328+
{
329+
PassportLogger.Debug($"{TAG} WebView console log: {message}");
330+
}
331+
332+
#endregion
333+
}
334+
#endif
335+
}

src/Packages/Passport/Runtime/Scripts/Private/UI/iOSPassportWebView.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)