Skip to content

Commit 978b162

Browse files
committed
feat: working mini app login.
1 parent aebe844 commit 978b162

File tree

14 files changed

+271
-146
lines changed

14 files changed

+271
-146
lines changed

apps/dashboard/next.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ const securityHeaders = [
2929
},
3030
{
3131
key: "X-Frame-Options",
32-
value: "SAMEORIGIN",
32+
// allow farcaster.xyz as a frame ancestor for the browser mini app
33+
value: "frame-ancestors 'self' https://farcaster.xyz; default 'self'",
3334
},
3435
{
3536
key: "Referrer-Policy",

apps/dashboard/public/.well-known/apple-app-site-association

Lines changed: 0 additions & 30 deletions
This file was deleted.

apps/dashboard/public/.well-known/assetlinks.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

apps/dashboard/public/.well-known/farcaster.json

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function GET() {
4+
return NextResponse.json({
5+
applinks: {
6+
apps: ["XYHGSFAMHX.com.thirdweb.demo"],
7+
details: [
8+
{
9+
appIDs: ["XYHGSFAMHX.com.thirdweb.demo"],
10+
components: [
11+
{
12+
"#": "no_universal_links",
13+
exclude: true,
14+
comment:
15+
"Matches any URL with a fragment that equals no_universal_links and instructs the system not to open it as a universal link.",
16+
},
17+
{
18+
"/": "/mobile-wallet-protocol",
19+
comment:
20+
"Matches any URL with a path that starts with /mobile-wallet-protocol for coinbase wallet mobile redirects.",
21+
},
22+
{
23+
"/": "/dashboard/*",
24+
exclude: true,
25+
comment: "no universal link for dashboard",
26+
},
27+
],
28+
},
29+
],
30+
},
31+
webcredentials: {
32+
apps: ["XYHGSFAMHX.com.thirdweb.demo"],
33+
},
34+
appclips: {},
35+
});
36+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export async function GET() {
2+
return "7B227073704964223A2239373943394538343346343131343044463144313834343232393232313734313034353044314339464446394437384337313531303944334643463542433731222C2276657273696F6E223A312C22637265617465644F6E223A313536363233343735303036312C227369676E6174757265223A22333038303036303932613836343838366637306430313037303261303830333038303032303130313331306633303064303630393630383634383031363530333034303230313035303033303830303630393261383634383836663730643031303730313030303061303830333038323033653333303832303338386130303330323031303230323038346333303431343935313964353433363330306130363038326138363438636533643034303330323330376133313265333032633036303335353034303330633235343137303730366336353230343137303730366336393633363137343639366636653230343936653734363536373732363137343639366636653230343334313230326432303437333333313236333032343036303335353034306230633164343137303730366336353230343336353732373436393636363936333631373436393666366532303431373537343638366637323639373437393331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533333031653137306433313339333033353331333833303331333333323335333735613137306433323334333033353331333633303331333333323335333735613330356633313235333032333036303335353034303330633163363536333633326437333664373032643632373236663662363537323264373336393637366535663535343333343264353035323466343433313134333031323036303335353034306230633062363934663533323035333739373337343635366437333331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533333035393330313330363037326138363438636533643032303130363038326138363438636533643033303130373033343230303034633231353737656465626436633762323231386636386464373039306131323138646337623062643666326332383364383436303935643934616634613534313162383334323065643831316633343037653833333331663163353463336637656233323230643662616435643465666634393238393839336537633066313361333832303231313330383230323064333030633036303335353164313330313031666630343032333030303330316630363033353531643233303431383330313638303134323366323439633434663933653465663237653663346636323836633366613262626664326534623330343530363038326230363031303530353037303130313034333933303337333033353036303832623036303130353035303733303031383632393638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635363136393633363133333330333233303832303131643036303335353164323030343832303131343330383230313130333038323031306330363039326138363438383666373633363430353031333038316665333038316333303630383262303630313035303530373032303233303831623630633831623335323635366336393631366536333635323036663665323037343638363937333230363336353732373436393636363936333631373436353230363237393230363136653739323037303631373237343739323036313733373337353664363537333230363136333633363537303734363136653633363532303666363632303734363836353230373436383635366532303631373037303663363936333631363236633635323037333734363136653634363137323634323037343635373236643733323036313665363432303633366636653634363937343639366636653733323036663636323037353733363532633230363336353732373436393636363936333631373436353230373036663663363936333739323036313665363432303633363537323734363936363639363336313734363936663665323037303732363136333734363936333635323037333734363137343635366436353665373437333265333033363036303832623036303130353035303730323031313632613638373437343730336132663266373737373737326536313730373036633635326536333666366432663633363537323734363936363639363336313734363536313735373436383666373236393734373932663330333430363033353531643166303432643330326233303239613032376130323538363233363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353631363936333631333332653633373236633330316430363033353531643065303431363034313439343537646236666435373438313836383938393736326637653537383530376537396235383234333030653036303335353164306630313031666630343034303330323037383033303066303630393261383634383836663736333634303631643034303230353030333030613036303832613836343863653364303430333032303334393030333034363032323130306265303935373166653731653165373335623535653561666163623463373266656234343566333031383532323263373235313030326236316562643666353530323231303064313862333530613564643664643665623137343630333562313165623263653837636661336536616636636264383338303839306463383263646461613633333038323032656533303832303237356130303330323031303230323038343936643266626633613938646139373330306130363038326138363438636533643034303330323330363733313162333031393036303335353034303330633132343137303730366336353230353236663666373432303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330316531373064333133343330333533303336333233333334333633333330356131373064333233393330333533303336333233333334333633333330356133303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030346630313731313834313964373634383564353161356532353831303737366538383061326566646537626165346465303864666334623933653133333536643536363562333561653232643039373736306432323465376262613038666437363137636538386362373662623636373062656338653832393834666635343435613338316637333038316634333034363036303832623036303130353035303730313031303433613330333833303336303630383262303630313035303530373330303138363261363837343734373033613266326636663633373337303265363137303730366336353265363336663664326636663633373337303330333432643631373037303663363537323666366637343633363136373333333031643036303335353164306530343136303431343233663234396334346639336534656632376536633466363238366333666132626266643265346233303066303630333535316431333031303166663034303533303033303130316666333031663036303335353164323330343138333031363830313462626230646561313538333338383961613438613939646562656264656261666461636232346162333033373036303335353164316630343330333032653330326361303261613032383836323636383734373437303361326632663633373236633265363137303730366336353265363336663664326636313730373036633635373236663666373436333631363733333265363337323663333030653036303335353164306630313031666630343034303330323031303633303130303630613261383634383836663736333634303630323065303430323035303033303061303630383261383634386365336430343033303230333637303033303634303233303361636637323833353131363939623138366662333563333536636136326266663431376564643930663735346461323865626566313963383135653432623738396638393866373962353939663938643534313064386639646539633266653032333033323264643534343231623061333035373736633564663333383362393036376664313737633263323136643936346663363732363938323132366635346638376137643162393963623962303938393231363130363939306630393932316430303030333138323031386233303832303138373032303130313330383138363330376133313265333032633036303335353034303330633235343137303730366336353230343137303730366336393633363137343639366636653230343936653734363536373732363137343639366636653230343334313230326432303437333333313236333032343036303335353034306230633164343137303730366336353230343336353732373436393636363936333631373436393666366532303431373537343638366637323639373437393331313333303131303630333535303430613063306134313730373036633635323034393665363332653331306233303039303630333535303430363133303235353533303230383463333034313439353139643534333633303064303630393630383634383031363530333034303230313035303061303831393533303138303630393261383634383836663730643031303930333331306230363039326138363438383666373064303130373031333031633036303932613836343838366637306430313039303533313066313730643331333933303338333133393331333733313332333333303561333032613036303932613836343838366637306430313039333433313164333031623330306430363039363038363438303136353033303430323031303530306131306130363038326138363438636533643034303330323330326630363039326138363438383666373064303130393034333132323034323062303731303365313430613462386231376262613230316130336163643036396234653431366232613263383066383661383338313435633239373566633131333030613036303832613836343863653364303430333032303434363330343430323230343639306264636637626461663833636466343934396534633035313039656463663334373665303564373261313264376335666538633033303033343464663032323032363764353863393365626233353031333836363062353730373938613064643731313734316262353864626436613138363633353038353431656565393035303030303030303030303030227D";
3+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function GET() {
4+
return NextResponse.json([
5+
{
6+
relation: [
7+
"delegate_permission/common.handle_all_urls",
8+
"delegate_permission/common.get_login_creds",
9+
],
10+
target: {
11+
namespace: "android_app",
12+
package_name: "com.thirdweb.demo",
13+
sha256_cert_fingerprints: [
14+
"40:EB:0D:96:57:F3:D8:1F:BA:87:B8:E4:26:E0:3A:DB:C8:35:96:A8:A2:B2:55:F0:B1:64:F1:39:F8:6F:7E:EB",
15+
],
16+
},
17+
},
18+
]);
19+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { NextResponse } from "next/server";
2+
3+
export async function GET() {
4+
try {
5+
const appUrl =
6+
process.env.NEXT_PUBLIC_APP_URL || "http://nebula---localhost:3000";
7+
const manifest = {
8+
accountAssociation: {
9+
header: process.env.NEXT_PUBLIC_FARCASTER_HEADER,
10+
payload: process.env.NEXT_PUBLIC_FARCASTER_PAYLOAD,
11+
signature: process.env.NEXT_PUBLIC_FARCASTER_SIGNATURE,
12+
},
13+
frame: {
14+
version: "1",
15+
name: "Nebula",
16+
iconUrl: `${appUrl}/assets/nebula/frame/ask-nebula-pfp.png`,
17+
imageUrl: `${appUrl}/assets/nebula/frame/frame.png`,
18+
homeUrl: appUrl,
19+
splashImageUrl: `${appUrl}/assets/nebula/frame/ask-nebula-pfp.png`,
20+
splashBackgroundColor: "#171B20",
21+
subtitle: "Web3 AI Assistant",
22+
description:
23+
"Ask questions about your web3 activity, get AI-powered insights about your wallet, and explore blockchain data through natural language",
24+
primaryCategory: "utility",
25+
tags: ["ai", "web3", "blockchain", "insights", "wallet"],
26+
tagline: "Your AI guide to web3",
27+
ogTitle: "Nebula - Web3 AI Assistant",
28+
ogDescription:
29+
"Interact with your wallet data through natural language queries powered by Thirdweb Nebula AI",
30+
heroImageUrl: `${appUrl}/assets/nebula/frame/screenshot.svg`,
31+
ogImageUrl: `${appUrl}/assets/nebula/frame/og.svg`,
32+
screenshotUrls: [
33+
`${appUrl}/assets/nebula/frame/screenshot.svg`,
34+
`${appUrl}/assets/nebula/frame/frame.png`,
35+
`${appUrl}/assets/nebula/frame/og.svg`,
36+
],
37+
},
38+
};
39+
return NextResponse.json(manifest);
40+
} catch (error) {
41+
console.error("Error generating manifest:", error);
42+
return NextResponse.json(
43+
{ error: (error as Error).message },
44+
{ status: 500 },
45+
);
46+
}
47+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"use client";
2+
3+
import type { AddMiniAppResult } from "@farcaster/frame-core/dist/actions/AddMiniApp";
4+
import type { FrameContext } from "@farcaster/frame-core/dist/context";
5+
import { sdk } from "@farcaster/frame-sdk";
6+
import { useQuery } from "@tanstack/react-query";
7+
import {
8+
type ReactNode,
9+
createContext,
10+
useCallback,
11+
useContext,
12+
useState,
13+
} from "react";
14+
import { base } from "thirdweb/chains";
15+
import { useConnect } from "thirdweb/react";
16+
import { EIP1193 } from "thirdweb/wallets";
17+
import { nebulaAppThirdwebClient } from "../utils/nebulaThirdwebClient";
18+
19+
interface MiniAppContextType {
20+
isMiniAppReady: boolean;
21+
isInMiniApp: boolean;
22+
context: FrameContext | null;
23+
setMiniAppReady: () => void;
24+
addMiniApp: () => Promise<AddMiniAppResult | null>;
25+
}
26+
27+
// eslint-disable-next-line no-restricted-syntax
28+
const MiniAppContext = createContext<MiniAppContextType | undefined>(undefined);
29+
30+
export function MiniAppProvider({
31+
addMiniAppOnLoad,
32+
children,
33+
}: {
34+
addMiniAppOnLoad?: boolean;
35+
children: ReactNode;
36+
}) {
37+
const [context, setContext] = useState<FrameContext | null>(null);
38+
const [isMiniAppReady, setIsMiniAppReady] = useState(false);
39+
const [isInMiniApp, setisInMiniApp] = useState(false);
40+
const [, setError] = useState<string | null>(null);
41+
const { connect } = useConnect();
42+
43+
const connectWallet = useCallback(async () => {
44+
connect(async () => {
45+
const wallet = EIP1193.fromProvider({ provider: sdk.wallet.ethProvider });
46+
await wallet.connect({ client: nebulaAppThirdwebClient, chain: base });
47+
return wallet;
48+
});
49+
}, [connect]);
50+
51+
const setMiniAppReady = useCallback(async () => {
52+
try {
53+
const isInMiniApp = await sdk.isInMiniApp();
54+
setisInMiniApp(isInMiniApp);
55+
if (!isInMiniApp) return;
56+
const context = await sdk.context;
57+
if (context) {
58+
setContext(context as FrameContext);
59+
} else {
60+
setError("Failed to load Farcaster context");
61+
}
62+
await sdk.actions.ready();
63+
if (sdk.wallet) {
64+
await connectWallet();
65+
}
66+
} catch (err) {
67+
setError(err instanceof Error ? err.message : "Failed to initialize SDK");
68+
console.error("SDK initialization error:", err);
69+
} finally {
70+
setIsMiniAppReady(true);
71+
}
72+
}, [connectWallet]);
73+
74+
useQuery({
75+
queryKey: ["miniapp-ready"],
76+
queryFn: async () => {
77+
try {
78+
await setMiniAppReady();
79+
return true;
80+
} catch (err) {
81+
setError(
82+
err instanceof Error ? err.message : "Failed to initialize SDK",
83+
);
84+
console.error("SDK initialization error:", err);
85+
return false;
86+
}
87+
},
88+
});
89+
90+
const handleAddMiniApp = useCallback(async () => {
91+
try {
92+
if (!isInMiniApp) return null;
93+
const result = await sdk.actions.addFrame();
94+
if (result) {
95+
return result;
96+
}
97+
return null;
98+
} catch (error) {
99+
console.error("[error] adding frame", error);
100+
return null;
101+
}
102+
}, [isInMiniApp]);
103+
104+
useQuery({
105+
queryKey: ["miniapp-add"],
106+
queryFn: async () => {
107+
try {
108+
await handleAddMiniApp();
109+
return true;
110+
} catch (error) {
111+
console.error("[error] adding frame", error);
112+
return false;
113+
}
114+
},
115+
enabled: isMiniAppReady && !context?.client?.added && addMiniAppOnLoad,
116+
});
117+
118+
return (
119+
<MiniAppContext.Provider
120+
value={{
121+
isMiniAppReady,
122+
setMiniAppReady,
123+
isInMiniApp,
124+
addMiniApp: handleAddMiniApp,
125+
context,
126+
}}
127+
>
128+
{children}
129+
</MiniAppContext.Provider>
130+
);
131+
}
132+
133+
export function useMiniApp() {
134+
const context = useContext(MiniAppContext);
135+
if (context === undefined) {
136+
throw new Error("useMiniApp must be used within a MiniAppProvider");
137+
}
138+
return context;
139+
}

apps/dashboard/src/app/nebula-app/(app)/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default async function Page(props: {
2222
]);
2323

2424
if (!authToken || !accountAddress) {
25+
console.log("redirecting to login");
2526
loginRedirect();
2627
}
2728

0 commit comments

Comments
 (0)