+4
.gitignore
+4
.gitignore
+37
docs/app/auth/complete/page.tsx
+37
docs/app/auth/complete/page.tsx
···
1
+
"use client";
2
+
3
+
import { useEffect } from "react";
4
+
import { useSearchParams } from "next/navigation";
5
+
6
+
export default function AuthComplete() {
7
+
const searchParams = useSearchParams();
8
+
9
+
useEffect(() => {
10
+
// Get all URL parameters
11
+
const params = new URLSearchParams();
12
+
searchParams.forEach((value, key) => {
13
+
params.append(key, value);
14
+
});
15
+
16
+
// Construct the redirect URL with all parameters
17
+
const redirectUrl = `atprotobackups://auth?${params.toString()}`;
18
+
19
+
// Open the URL in the system's default handler
20
+
window.location.href = redirectUrl;
21
+
}, [searchParams]);
22
+
23
+
return (
24
+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
25
+
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow-md">
26
+
<div className="text-center">
27
+
<h2 className="mt-6 text-3xl font-bold text-gray-900">
28
+
Authentication Complete
29
+
</h2>
30
+
<p className="mt-2 text-sm text-gray-600">
31
+
Redirecting you back to the application...
32
+
</p>
33
+
</div>
34
+
</div>
35
+
</div>
36
+
);
37
+
}
+1
-1
docs/public/client_metadata.json
+1
-1
docs/public/client_metadata.json
+1
src-tauri/capabilities/default.json
+1
src-tauri/capabilities/default.json
-14
src-tauri/lib.rs
-14
src-tauri/lib.rs
···
1
-
#[cfg_attr(mobile, tauri::mobile_entry_point)]
2
-
pub fn run() {
3
-
let mut builder = tauri::Builder::default();
4
-
5
-
#[cfg(desktop)]
6
-
{
7
-
builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
8
-
println!("a new app instance was opened with {argv:?} and the deep link event was already triggered");
9
-
// when defining deep link schemes at runtime, you must also check `argv` here
10
-
}));
11
-
}
12
-
13
-
builder = builder.plugin(tauri_plugin_deep_link::init());
14
-
}
+19
-1
src-tauri/src/lib.rs
+19
-1
src-tauri/src/lib.rs
···
1
1
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
2
+
use tauri_plugin_deep_link::DeepLinkExt;
3
+
2
4
#[tauri::command]
3
5
fn greet(name: &str) -> String {
4
6
format!("Hello, {}! You've been greeted from Rust!", name)
···
6
8
7
9
#[cfg_attr(mobile, tauri::mobile_entry_point)]
8
10
pub fn run() {
9
-
tauri::Builder::default()
11
+
let mut builder = tauri::Builder::default()
10
12
.plugin(tauri_plugin_deep_link::init())
11
13
.plugin(tauri_plugin_opener::init())
12
14
.invoke_handler(tauri::generate_handler![greet])
15
+
.setup(|app| {
16
+
#[cfg(any(windows, target_os = "linux"))]
17
+
{
18
+
app.deep_link().register_all()?;
19
+
}
20
+
Ok(())
21
+
});
22
+
23
+
#[cfg(desktop)]
24
+
{
25
+
builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
26
+
println!("A new app instance was opened with {argv:?} and the deep link event was already triggered.");
27
+
}));
28
+
}
29
+
30
+
builder
13
31
.run(tauri::generate_context!())
14
32
.expect("error while running tauri application");
15
33
}
+5
-3
src/App.tsx
+5
-3
src/App.tsx
···
3
3
import { invoke } from "@tauri-apps/api/core";
4
4
import "./App.css";
5
5
import { Button } from "./components/ui/button";
6
+
import LoginPage from "./routes/Login";
6
7
7
8
function App() {
8
9
const [greetMsg, setGreetMsg] = useState("");
···
14
15
}
15
16
16
17
return (
17
-
<main className="container dark">
18
-
<div className="titlebar">
19
-
<div data-tauri-drag-region></div>
18
+
<main className="dark">
19
+
<div className="titlebar" data-tauri-drag-region>
20
20
<div className="controls">
21
21
<Button variant="ghost" id="titlebar-minimize" title="minimize">
22
22
<svg
···
53
53
</button>
54
54
</div>
55
55
</div>
56
+
57
+
<LoginPage />
56
58
</main>
57
59
);
58
60
}
+10
-43
src/routes/Login.tsx
+10
-43
src/routes/Login.tsx
···
7
7
BrowserOAuthClient,
8
8
BrowserOAuthClientOptions,
9
9
} from "@atproto/oauth-client-browser";
10
+
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
10
11
11
12
type LoginMethod = "credential" | "oauth";
12
13
···
24
25
useEffect(() => {
25
26
const initOAuthClient = async () => {
26
27
try {
27
-
const client = new BrowserOAuthClient({
28
-
clientMetadata: {
29
-
client_id: "http://localhost:3000", // Replace with your actual client ID
30
-
redirect_uris: ["http://localhost:3000/callback"], // Replace with your redirect URI
31
-
client_name: "ATProto Backup App",
32
-
client_uri: "http://localhost:3000",
33
-
scope: "atproto",
34
-
},
28
+
const client = await BrowserOAuthClient.load({
29
+
clientId: "https://atproto-backup.pages.dev/client_metadata.json",
35
30
});
36
31
setOauthClient(client);
37
32
} catch (err) {
···
41
36
initOAuthClient();
42
37
}, []);
43
38
44
-
const handleCredentialLogin = async () => {
45
-
setLoading(true);
46
-
setError("");
47
-
const agent = new Agent(new CredentialSession());
48
-
49
-
try {
50
-
const result = await agent.login({ identifier, password });
51
-
console.log("Credential login successful!", result);
52
-
// Store session, redirect, etc.
53
-
} catch (err: any) {
54
-
console.error(err);
55
-
setError(err.message || "Credential login failed");
56
-
} finally {
57
-
setLoading(false);
58
-
}
59
-
};
60
-
61
-
const handleOAuthLogin = async () => {
39
+
const handleLogin = async () => {
62
40
if (!oauthClient) {
63
41
setError("OAuth client not initialized");
64
42
return;
···
135
113
136
114
handleCallback();
137
115
}, [oauthClient]);
138
-
139
-
const handleLogin = () => {
140
-
if (loginMethod === "credential") {
141
-
handleCredentialLogin();
142
-
} else {
143
-
handleOAuthLogin();
144
-
}
145
-
};
146
-
147
116
return (
148
117
<div className="min-h-screen flex items-center justify-center bg-background px-4">
149
118
<Card className="w-full max-w-sm">
150
119
<CardHeader>
151
-
<CardTitle>Login to Bluesky</CardTitle>
120
+
<CardTitle>Login to your Bluesky account</CardTitle>
152
121
</CardHeader>
153
122
<CardContent className="space-y-4">
154
123
<Input
···
156
125
value={identifier}
157
126
onChange={(e) => setIdentifier(e.target.value)}
158
127
/>
159
-
<Input
160
-
type="password"
161
-
placeholder="Password"
162
-
value={password}
163
-
onChange={(e) => setPassword(e.target.value)}
164
-
/>
165
128
{error && <p className="text-sm text-red-500">{error}</p>}
166
-
<Button className="w-full" onClick={handleLogin} disabled={loading}>
129
+
<Button
130
+
className="w-full"
131
+
onClick={handleLogin}
132
+
disabled={loading || identifier == null}
133
+
>
167
134
{loading ? "Logging in..." : "Login"}
168
135
</Button>
169
136
</CardContent>