+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
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
2
#[tauri::command]
3
fn greet(name: &str) -> String {
4
format!("Hello, {}! You've been greeted from Rust!", name)
···
6
7
#[cfg_attr(mobile, tauri::mobile_entry_point)]
8
pub fn run() {
9
-
tauri::Builder::default()
10
.plugin(tauri_plugin_deep_link::init())
11
.plugin(tauri_plugin_opener::init())
12
.invoke_handler(tauri::generate_handler![greet])
13
.run(tauri::generate_context!())
14
.expect("error while running tauri application");
15
}
···
1
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
2
+
use tauri_plugin_deep_link::DeepLinkExt;
3
+
4
#[tauri::command]
5
fn greet(name: &str) -> String {
6
format!("Hello, {}! You've been greeted from Rust!", name)
···
8
9
#[cfg_attr(mobile, tauri::mobile_entry_point)]
10
pub fn run() {
11
+
let mut builder = tauri::Builder::default()
12
.plugin(tauri_plugin_deep_link::init())
13
.plugin(tauri_plugin_opener::init())
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
31
.run(tauri::generate_context!())
32
.expect("error while running tauri application");
33
}
+5
-3
src/App.tsx
+5
-3
src/App.tsx
···
3
import { invoke } from "@tauri-apps/api/core";
4
import "./App.css";
5
import { Button } from "./components/ui/button";
6
7
function App() {
8
const [greetMsg, setGreetMsg] = useState("");
···
14
}
15
16
return (
17
-
<main className="container dark">
18
-
<div className="titlebar">
19
-
<div data-tauri-drag-region></div>
20
<div className="controls">
21
<Button variant="ghost" id="titlebar-minimize" title="minimize">
22
<svg
···
53
</button>
54
</div>
55
</div>
56
</main>
57
);
58
}
···
3
import { invoke } from "@tauri-apps/api/core";
4
import "./App.css";
5
import { Button } from "./components/ui/button";
6
+
import LoginPage from "./routes/Login";
7
8
function App() {
9
const [greetMsg, setGreetMsg] = useState("");
···
15
}
16
17
return (
18
+
<main className="dark">
19
+
<div className="titlebar" data-tauri-drag-region>
20
<div className="controls">
21
<Button variant="ghost" id="titlebar-minimize" title="minimize">
22
<svg
···
53
</button>
54
</div>
55
</div>
56
+
57
+
<LoginPage />
58
</main>
59
);
60
}
+10
-43
src/routes/Login.tsx
+10
-43
src/routes/Login.tsx
···
7
BrowserOAuthClient,
8
BrowserOAuthClientOptions,
9
} from "@atproto/oauth-client-browser";
10
11
type LoginMethod = "credential" | "oauth";
12
···
24
useEffect(() => {
25
const initOAuthClient = async () => {
26
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
-
},
35
});
36
setOauthClient(client);
37
} catch (err) {
···
41
initOAuthClient();
42
}, []);
43
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 () => {
62
if (!oauthClient) {
63
setError("OAuth client not initialized");
64
return;
···
135
136
handleCallback();
137
}, [oauthClient]);
138
-
139
-
const handleLogin = () => {
140
-
if (loginMethod === "credential") {
141
-
handleCredentialLogin();
142
-
} else {
143
-
handleOAuthLogin();
144
-
}
145
-
};
146
-
147
return (
148
<div className="min-h-screen flex items-center justify-center bg-background px-4">
149
<Card className="w-full max-w-sm">
150
<CardHeader>
151
-
<CardTitle>Login to Bluesky</CardTitle>
152
</CardHeader>
153
<CardContent className="space-y-4">
154
<Input
···
156
value={identifier}
157
onChange={(e) => setIdentifier(e.target.value)}
158
/>
159
-
<Input
160
-
type="password"
161
-
placeholder="Password"
162
-
value={password}
163
-
onChange={(e) => setPassword(e.target.value)}
164
-
/>
165
{error && <p className="text-sm text-red-500">{error}</p>}
166
-
<Button className="w-full" onClick={handleLogin} disabled={loading}>
167
{loading ? "Logging in..." : "Login"}
168
</Button>
169
</CardContent>
···
7
BrowserOAuthClient,
8
BrowserOAuthClientOptions,
9
} from "@atproto/oauth-client-browser";
10
+
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
11
12
type LoginMethod = "credential" | "oauth";
13
···
25
useEffect(() => {
26
const initOAuthClient = async () => {
27
try {
28
+
const client = await BrowserOAuthClient.load({
29
+
clientId: "https://atproto-backup.pages.dev/client_metadata.json",
30
});
31
setOauthClient(client);
32
} catch (err) {
···
36
initOAuthClient();
37
}, []);
38
39
+
const handleLogin = async () => {
40
if (!oauthClient) {
41
setError("OAuth client not initialized");
42
return;
···
113
114
handleCallback();
115
}, [oauthClient]);
116
return (
117
<div className="min-h-screen flex items-center justify-center bg-background px-4">
118
<Card className="w-full max-w-sm">
119
<CardHeader>
120
+
<CardTitle>Login to your Bluesky account</CardTitle>
121
</CardHeader>
122
<CardContent className="space-y-4">
123
<Input
···
125
value={identifier}
126
onChange={(e) => setIdentifier(e.target.value)}
127
/>
128
{error && <p className="text-sm text-red-500">{error}</p>}
129
+
<Button
130
+
className="w-full"
131
+
onClick={handleLogin}
132
+
disabled={loading || identifier == null}
133
+
>
134
{loading ? "Logging in..." : "Login"}
135
</Button>
136
</CardContent>