Skip to content
Draft
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
2 changes: 0 additions & 2 deletions apps/maple-research/frontend/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions apps/maple-research/frontend/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ maple-sdk = { path = "../../../../sdk/rust" }
# receive null objects from real Android launch/deep-link flows, aborting the
# process before tauri-plugin-deep-link sees OAuth/payment redirects. Remove
# this once Tauri resolves to a Tao release containing the upstream fix.
#
# It also carries tauri-apps/tao#1376 for iOS: in the scene life cycle a URL that
# cold-launches the app arrives in the scene connection options, which tao 0.37
# ignores, so OAuth callbacks that relaunch Maple were dropped.
# The vendored library uses this application's Cargo.lock; its upstream
# standalone lockfile is intentionally omitted.
tao = { path = "patches/tao-0.37.1" }
# tauri-plugin-sign-in-with-apple 1.0.2 (last release 2024-11) declares its Swift
# entry point `@_cdecl("init_plugin_sign_in_with_apple") func initPlugin()` with
# internal visibility. Xcode 27 release builds need these entry points `public`
# to link (Tauri 2.12.3 made its own public: tauri-apps/tauri#16130); the
# vendored copy only adds that keyword. Remove once upstream publishes the fix.
tauri-plugin-sign-in-with-apple = { path = "patches/tauri-plugin-sign-in-with-apple-1.0.2" }
25 changes: 25 additions & 0 deletions apps/maple-research/frontend/src-tauri/Info.ios.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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>
<!-- iOS 27 SDK builds must adopt the UIScene life cycle or UIKit aborts at launch.
One window only: extra scenes would have no WebView and render black. -->
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>TaoSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use std::{
};

use objc2::{rc::Retained, runtime::AnyObject, MainThreadMarker, Message};
use objc2_ui_kit::{UIApplication, UIScene, UISceneConnectionOptions, UIWindowScene};
use objc2_foundation::{NSSet, NSUserActivity};
use objc2_ui_kit::{
UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UIWindowScene,
};

use crate::{
dpi::LogicalSize,
Expand All @@ -28,7 +31,7 @@ use crate::{
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger,
NSOperatingSystemVersion, NSUInteger,
},
scene::scene_lifecycle_enabled,
scene::{self, scene_lifecycle_enabled},
},
window::WindowId as RootWindowId,
};
Expand Down Expand Up @@ -561,6 +564,28 @@ pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions)
let () = msg_send![window, release];
}
}

// a URL that launches the app (or opens a new scene) is handed over here, in the connection
// options: scene:openURLContexts: is only called for URLs received by a scene that is already
// connected, so without this they would be lost
//
// UIKit returns nil for both sets when the app is launched without a link, although the objc2
// bindings declare them non-null: read them as optional, the bindings would panic on nil
let url_contexts: Option<Retained<NSSet<UIOpenURLContext>>> = msg_send![options, URLContexts];
let user_activities: Option<Retained<NSSet<NSUserActivity>>> = msg_send![options, userActivities];
let source = "the scene connection options";
let mut urls = url_contexts
.map(|contexts| scene::urls_from_contexts(&contexts, source))
.unwrap_or_default();
urls.extend(
user_activities
.iter()
.flat_map(|activities| activities.iter())
.filter_map(|activity| scene::universal_link_url(&activity, source)),
);
if !urls.is_empty() {
handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls }));
}
}

pub unsafe fn register_window_for_scene(window: id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly};
use objc2_foundation::{
NSBundle, NSError, NSObject, NSObjectProtocol, NSSet, NSString, NSUserActivity,
NSUserActivityTypeBrowsingWeb,
};
use objc2_ui_kit::{
UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate,
Expand Down Expand Up @@ -59,6 +60,45 @@ unsafe fn handle_scene_window_events(scene: &UIScene, event: impl Fn() -> Window
}
}

// parses the URLs carried by the given contexts, skipping (and logging) the ones that are not
// valid; `source` names the delivery path in the log
pub unsafe fn urls_from_contexts(
url_contexts: &NSSet<UIOpenURLContext>,
source: &str,
) -> Vec<url::Url> {
url_contexts
.iter()
.filter_map(|ctx| {
ctx
.URL()
.absoluteString()
.and_then(|url| parse_url(&url.to_string(), source))
})
.collect()
}

// the URL of a universal link activity, if `activity` is one: other activities, such as
// Handoff, can carry a web page URL too and are not links that opened the app
pub unsafe fn universal_link_url(activity: &NSUserActivity, source: &str) -> Option<url::Url> {
if !activity
.activityType()
.isEqualToString(NSUserActivityTypeBrowsingWeb)
{
return None;
}
activity
.webpageURL()
.and_then(|url| url.absoluteString())
.and_then(|url| parse_url(&url.to_string(), source))
}

fn parse_url(url: &str, source: &str) -> Option<url::Url> {
url
.parse()
.map_err(|e| log::error!("failed to parse URL {url} from {source}: {e}"))
.ok()
}

define_class!(
#[unsafe(super(NSObject))]
#[name = "TaoSceneDelegate"]
Expand Down Expand Up @@ -125,21 +165,7 @@ define_class!(
#[unsafe(method(scene:openURLContexts:))]
fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet<UIOpenURLContext>) {
unsafe {
let urls: Vec<url::Url> = url_contexts
.iter()
.filter_map(|ctx| {
ctx.URL().absoluteString().and_then(|url| {
let url = url.to_string();
url
.parse()
.map_err(|e| {
log::error!("failed to parse URL {url} from scene:openURLContexts: {e}");
e
})
.ok()
})
})
.collect();
let urls = urls_from_contexts(url_contexts, "scene:openURLContexts:");
if !urls.is_empty() {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls }));
}
Expand Down Expand Up @@ -174,11 +200,7 @@ define_class!(
fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) {
unsafe {
// universal app links
if let Some(url) = user_activity
.webpageURL()
.and_then(|url| url.absoluteString())
{
let url = url.to_string().parse::<url::Url>().unwrap();
if let Some(url) = universal_link_url(user_activity, "scene:continueUserActivity:") {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened {
urls: vec![url],
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":1}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.tauri/
/ios/.build/
/ios/Package.resolved
/target/
/Cargo.lock
/permissions/schemas/
/permissions/autogenerated/
/android/.gradle/
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.

[package]
edition = "2021"
rust-version = "1.77.2"
name = "tauri-plugin-sign-in-with-apple"
version = "1.0.2"
authors = ["Manaf941"]
build = "build.rs"
links = "tauri-plugin-sign-in-with-apple"
exclude = [
"/examples",
"/webview-dist",
"/webview-src",
"/node_modules",
]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Add Sign In With Apple capabilities to your Tauri iOS App."
readme = "README.md"
license = "MIT"

[lib]
name = "tauri_plugin_sign_in_with_apple"
path = "src/lib.rs"

[dependencies.serde]
version = "1.0"

[dependencies.tauri]
version = "2.1.0"

[dependencies.thiserror]
version = "2"

[build-dependencies.tauri-plugin]
version = "2.0.2"
features = ["build"]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2024 Manaf Mhamdi Alaoui

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Tauri Plugin sign_in_with_apple

probably unstable, use with caution !

For now, only iOS works.

For other platform, you should probably use the Sign In with Apple Web SDK.

https://developer.apple.com/help/account/configure-app-capabilities/configure-sign-in-with-apple-for-the-web/

in case of any question, contact me:
Discord: @manaf941
Twitter: @manaaaaaaaf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build
/.tauri
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}

android {
namespace = "ch.manaf.tauri_plugins.sign_in_with_apple"
compileSdk = 34

defaultConfig {
minSdk = 21

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {

implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation(project(":tauri-android"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
google()
}
resolutionStrategy {
eachPlugin {
switch (requested.id.id) {
case "com.android.library":
useVersion("8.0.2")
break
case "org.jetbrains.kotlin.android":
useVersion("1.8.20")
break
}
}
}
}

dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()

}
}

include ':tauri-android'
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
Loading
Loading