Skip to content
Open
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
Binary file removed app/zoneinfo.zip
Binary file not shown.
945 changes: 945 additions & 0 deletions iOS对接OIDC技术.md

Large diffs are not rendered by default.

1,254 changes: 1,254 additions & 0 deletions iOS打包流程.md

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions iosk.xcframework/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>Iosk.framework/Iosk</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>Iosk.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>Iosk.framework/Iosk</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>Iosk.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion siyuan-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
268CE5AD275A68B700DFCFF8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
268CE5AF275A68B700DFCFF8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
268CE5B5275A68F300DFCFF8 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = "<group>"; };
26D64CCD28430F4000295E70 /* iosk.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = iosk.xcframework; path = ../siyuan/kernel/ios/iosk.xcframework; sourceTree = "<group>"; };
26D64CCD28430F4000295E70 /* iosk.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = iosk.xcframework; path = iosk.xcframework; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down
Binary file not shown.
98 changes: 94 additions & 4 deletions siyuan-ios/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

// Cache to prevent duplicate OIDC callback processing
private var lastProcessedOIDCURL: String? = nil
private var pendingOIDCCallback: String? = nil


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Expand All @@ -29,19 +33,105 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
for context in connectionOptions.urlContexts{
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
ViewController.syWebView.evaluateJavaScript("openFileByURL('" + context.url.absoluteString + "')")
}
handleURLContext(context)
}
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for context in URLContexts{
handleURLContext(context)
}
}

// Handle URL context for both OIDC callbacks and block URLs
private func handleURLContext(_ context: UIOpenURLContext) {
let url = context.url

// Check if this is an OIDC callback
// OIDC callbacks typically contain certain query parameters or paths
if isOIDCCallback(url) {
let urlString = url.absoluteString

// Prevent duplicate processing
if urlString == lastProcessedOIDCURL {
print("OIDC callback already processed, skipping: \(urlString)")
return
}

lastProcessedOIDCURL = urlString
pendingOIDCCallback = urlString

// Use robust callback injection with retry mechanism
callHandleOidcCallback(urlString)
} else {
// Handle regular block URL
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
ViewController.syWebView.evaluateJavaScript("openFileByURL('" + context.url.absoluteString + "')")
ViewController.syWebView.evaluateJavaScript("window.openFileByURL('" + url.absoluteString + "')")
}
}
}

// Check if the URL is an OIDC callback
private func isOIDCCallback(_ url: URL) -> Bool {
// Priority 1: Check if path contains "oidc-callback" (consistent with Android)
if url.path.contains("oidc-callback") {
return true
}

// Priority 2: Check if URL contains typical OIDC callback parameters
// Common OIDC callback parameters: code, state, error, error_description
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else {
return false
}

// Check for common OIDC callback parameters
let oidcParams = ["code", "state", "error", "error_description", "id_token", "access_token"]
return queryItems.contains { item in
oidcParams.contains(item.name)
}
}

// Robust callback injection with retry mechanism
private func callHandleOidcCallback(_ urlString: String, retryCount: Int = 0) {
let escapedURL = escapeJavaScriptString(urlString)

// Check if the callback function exists
ViewController.syWebView.evaluateJavaScript("typeof window.handleOidcCallbackLink === 'function'") { [weak self] result, error in
guard let self = self else { return }

if let isFunction = result as? Bool, isFunction {
// Function exists, call it immediately
ViewController.syWebView.evaluateJavaScript("window.handleOidcCallbackLink('\(escapedURL)')") { result, error in
if let error = error {
print("Error calling handleOidcCallbackLink: \(error)")
} else {
print("Successfully called handleOidcCallbackLink")
self.pendingOIDCCallback = nil
}
}
} else if retryCount < 10 {
// Function doesn't exist yet, retry after delay (max 5 seconds)
print("handleOidcCallbackLink not ready, retry \(retryCount + 1)/10")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.callHandleOidcCallback(urlString, retryCount: retryCount + 1)
}
} else {
print("Failed to call handleOidcCallbackLink after \(retryCount) retries")
self.pendingOIDCCallback = nil
}
}
}

// Escape JavaScript string to prevent injection
private func escapeJavaScriptString(_ string: String) -> String {
return string
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "'", with: "\\'")
.replacingOccurrences(of: "\"", with: "\\\"")
.replacingOccurrences(of: "\n", with: "\\n")
.replacingOccurrences(of: "\r", with: "\\r")
}


func sceneDidDisconnect(_ scene: UIScene) {
Expand Down
40 changes: 39 additions & 1 deletion siyuan-ios/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import WebKit
import Iosk
import PDFKit
import GameController
import SafariServices

class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, WKScriptMessageHandler, UIPrintInteractionControllerDelegate {
class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, WKScriptMessageHandler, UIPrintInteractionControllerDelegate, SFSafariViewControllerDelegate {

static let iapManager = IAPManager.shared
static let syWebView = WKWebView()
Expand Down Expand Up @@ -71,6 +72,7 @@ class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelega
ViewController.syWebView.configuration.userContentController.add(self, name: "changeStatusBar")
ViewController.syWebView.configuration.userContentController.add(self, name: "setClipboard")
ViewController.syWebView.configuration.userContentController.add(self, name: "openLink")
ViewController.syWebView.configuration.userContentController.add(self, name: "openAuthURL")
ViewController.syWebView.configuration.userContentController.add(self, name: "purchase")
ViewController.syWebView.configuration.userContentController.add(self, name: "print")
ViewController.syWebView.configuration.userContentController.add(self, name: "exit")
Expand Down Expand Up @@ -139,6 +141,8 @@ class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelega
if let url = NSURL(string: message.body as! String) {
UIApplication.shared.open(url as URL, options: [:], completionHandler: nil)
}
} else if message.name == "openAuthURL" {
openAuthURL(message.body as! String)
} else if message.name == "purchase" {
let argument = (message.body as! String).split(separator: " ");
for pItem in IAPManager.shared.products {
Expand Down Expand Up @@ -270,6 +274,40 @@ class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelega
task.resume()
}

// Open authentication URL using SFSafariViewController
// Similar to Android's Custom Tabs implementation
private func openAuthURL(_ urlString: String) {
// Validate URL
guard !urlString.isEmpty, !urlString.hasPrefix("#") else {
print("openAuthURL failed: invalid url")
return
}

guard let url = URL(string: urlString) else {
print("openAuthURL failed: cannot parse url")
return
}

// Validate scheme (only http/https allowed)
guard let scheme = url.scheme?.lowercased(),
scheme == "http" || scheme == "https" else {
print("openAuthURL failed: only support http/https protocol, not \(url.scheme ?? "nil")")
return
}

// Use SFSafariViewController (iOS equivalent of Chrome Custom Tabs)
let safariVC = SFSafariViewController(url: url)
safariVC.delegate = self

// Present the Safari view controller
self.present(safariVC, animated: true, completion: nil)
}

// SFSafariViewControllerDelegate method
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismiss(animated: true, completion: nil)
}

private func printDynamicHTML(_ htmlContent: String) {
let argument = htmlContent.split(separator: "​");
self.printTitle = String(argument[0])
Expand Down