@open-federation/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +197 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
OpenFederationProvider: () => OpenFederationProvider,
|
|
24
|
+
SignInWithOpenFederation: () => SignInWithOpenFederation,
|
|
25
|
+
useOFClient: () => useOFClient,
|
|
26
|
+
useOFSession: () => useOFSession,
|
|
27
|
+
useOFWallet: () => useOFWallet
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/provider.tsx
|
|
32
|
+
var import_react = require("react");
|
|
33
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
34
|
+
var OpenFederationContext = (0, import_react.createContext)(null);
|
|
35
|
+
function OpenFederationProvider({ client, children }) {
|
|
36
|
+
const [user, setUser] = (0, import_react.useState)(null);
|
|
37
|
+
const [ready, setReady] = (0, import_react.useState)(false);
|
|
38
|
+
(0, import_react.useEffect)(() => {
|
|
39
|
+
let mounted = true;
|
|
40
|
+
(async () => {
|
|
41
|
+
const u = await client.getUser();
|
|
42
|
+
if (!mounted) return;
|
|
43
|
+
setUser(u);
|
|
44
|
+
setReady(true);
|
|
45
|
+
})();
|
|
46
|
+
const unsubscribe = client.onAuthChange((u) => {
|
|
47
|
+
if (mounted) setUser(u);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
mounted = false;
|
|
51
|
+
unsubscribe();
|
|
52
|
+
};
|
|
53
|
+
}, [client]);
|
|
54
|
+
const value = (0, import_react.useMemo)(() => ({ client, user, ready }), [client, user, ready]);
|
|
55
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OpenFederationContext.Provider, { value, children });
|
|
56
|
+
}
|
|
57
|
+
function useOpenFederationContext() {
|
|
58
|
+
const ctx = (0, import_react.useContext)(OpenFederationContext);
|
|
59
|
+
if (!ctx) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"OpenFederation hooks/components must be used inside <OpenFederationProvider>. Wrap your app root with it."
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return ctx;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/hooks.ts
|
|
68
|
+
var import_react2 = require("react");
|
|
69
|
+
function useOFClient() {
|
|
70
|
+
return useOpenFederationContext().client;
|
|
71
|
+
}
|
|
72
|
+
function useOFSession() {
|
|
73
|
+
const { client, user, ready } = useOpenFederationContext();
|
|
74
|
+
const login = (0, import_react2.useCallback)((opts) => client.login(opts), [client]);
|
|
75
|
+
const register = (0, import_react2.useCallback)((opts) => client.register(opts), [client]);
|
|
76
|
+
const logout = (0, import_react2.useCallback)(() => client.logout(), [client]);
|
|
77
|
+
return {
|
|
78
|
+
user,
|
|
79
|
+
ready,
|
|
80
|
+
isAuthenticated: user !== null,
|
|
81
|
+
client,
|
|
82
|
+
login,
|
|
83
|
+
register,
|
|
84
|
+
logout
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function useOFWallet() {
|
|
88
|
+
const { client, user } = useOpenFederationContext();
|
|
89
|
+
const [wallets, setWallets] = (0, import_react2.useState)([]);
|
|
90
|
+
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
91
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
92
|
+
const refresh = (0, import_react2.useCallback)(async () => {
|
|
93
|
+
if (!user) {
|
|
94
|
+
setWallets([]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
try {
|
|
100
|
+
const res = await client.listWalletLinks();
|
|
101
|
+
setWallets(res.walletLinks);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
setError(err);
|
|
104
|
+
} finally {
|
|
105
|
+
setLoading(false);
|
|
106
|
+
}
|
|
107
|
+
}, [client, user]);
|
|
108
|
+
(0, import_react2.useEffect)(() => {
|
|
109
|
+
refresh();
|
|
110
|
+
}, [refresh]);
|
|
111
|
+
const signIn = (0, import_react2.useCallback)(
|
|
112
|
+
(opts) => client.signInWithOpenFederation(opts),
|
|
113
|
+
[client]
|
|
114
|
+
);
|
|
115
|
+
return { wallets, loading, error, refresh, signIn };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/sign-in-button.tsx
|
|
119
|
+
var import_react3 = require("react");
|
|
120
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
121
|
+
var DEFAULT_STYLE = {
|
|
122
|
+
display: "inline-flex",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
justifyContent: "center",
|
|
125
|
+
gap: "0.5rem",
|
|
126
|
+
padding: "0.625rem 1rem",
|
|
127
|
+
border: "1px solid rgba(0,0,0,0.1)",
|
|
128
|
+
borderRadius: "0.5rem",
|
|
129
|
+
background: "#111",
|
|
130
|
+
color: "#fff",
|
|
131
|
+
fontWeight: 600,
|
|
132
|
+
cursor: "pointer"
|
|
133
|
+
};
|
|
134
|
+
function SignInWithOpenFederation(props) {
|
|
135
|
+
const { client, user } = useOpenFederationContext();
|
|
136
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
137
|
+
const disabled = !user || loading;
|
|
138
|
+
const handleClick = (0, import_react3.useCallback)(async () => {
|
|
139
|
+
if (disabled) return;
|
|
140
|
+
setLoading(true);
|
|
141
|
+
try {
|
|
142
|
+
let walletAddress = props.walletAddress;
|
|
143
|
+
if (!walletAddress) {
|
|
144
|
+
const list = await client.listWalletLinks();
|
|
145
|
+
const primaryOnChain = list.walletLinks.find(
|
|
146
|
+
(w) => w.chain === props.chain
|
|
147
|
+
);
|
|
148
|
+
if (!primaryOnChain) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`No wallet found on chain "${props.chain}" for this account. Provision or link one first.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
walletAddress = primaryOnChain.walletAddress;
|
|
154
|
+
}
|
|
155
|
+
const audience = props.audience ?? (typeof window !== "undefined" ? window.location.origin : void 0);
|
|
156
|
+
if (!audience) {
|
|
157
|
+
throw new Error("audience is required (no window.location available)");
|
|
158
|
+
}
|
|
159
|
+
const assertion = await client.signInWithOpenFederation({
|
|
160
|
+
chain: props.chain,
|
|
161
|
+
walletAddress,
|
|
162
|
+
audience,
|
|
163
|
+
statement: props.statement
|
|
164
|
+
});
|
|
165
|
+
props.onSuccess(assertion);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (props.onError) props.onError(err);
|
|
168
|
+
else throw err;
|
|
169
|
+
} finally {
|
|
170
|
+
setLoading(false);
|
|
171
|
+
}
|
|
172
|
+
}, [client, disabled, props]);
|
|
173
|
+
if (props.render) {
|
|
174
|
+
return props.render({ onClick: handleClick, loading, disabled });
|
|
175
|
+
}
|
|
176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
177
|
+
"button",
|
|
178
|
+
{
|
|
179
|
+
type: "button",
|
|
180
|
+
onClick: handleClick,
|
|
181
|
+
disabled,
|
|
182
|
+
className: props.className,
|
|
183
|
+
style: { ...DEFAULT_STYLE, opacity: disabled ? 0.6 : 1, ...props.style ?? {} },
|
|
184
|
+
"data-openfederation-signin": true,
|
|
185
|
+
children: loading ? "Signing\u2026" : props.label ?? "Sign in with OpenFederation"
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
190
|
+
0 && (module.exports = {
|
|
191
|
+
OpenFederationProvider,
|
|
192
|
+
SignInWithOpenFederation,
|
|
193
|
+
useOFClient,
|
|
194
|
+
useOFSession,
|
|
195
|
+
useOFWallet
|
|
196
|
+
});
|
|
197
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/hooks.ts","../src/sign-in-button.tsx"],"sourcesContent":["export { OpenFederationProvider } from './provider.js';\nexport type { OpenFederationProviderProps } from './provider.js';\nexport { useOFClient, useOFSession, useOFWallet } from './hooks.js';\nexport type { OFSession, OFWallets } from './hooks.js';\nexport { SignInWithOpenFederation } from './sign-in-button.js';\nexport type { SignInWithOpenFederationProps } from './sign-in-button.js';\n","import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';\nimport type { OpenFederationClient, User } from '@open-federation/sdk';\n\nexport interface OpenFederationProviderProps {\n /** A client instance created via `createClient(...)` from @open-federation/sdk. */\n client: OpenFederationClient;\n children: React.ReactNode;\n}\n\ninterface OpenFederationContextValue {\n client: OpenFederationClient;\n user: User | null;\n ready: boolean;\n}\n\nconst OpenFederationContext = createContext<OpenFederationContextValue | null>(null);\n\n/**\n * Wrap your React tree in this. The provider subscribes to the SDK's auth\n * state and exposes the current user through `useOFSession()`.\n *\n * @example\n * ```tsx\n * import { createClient } from '@open-federation/sdk';\n * import { OpenFederationProvider } from '@open-federation/react';\n *\n * const client = createClient({ serverUrl, partnerKey });\n *\n * <OpenFederationProvider client={client}>\n * <App />\n * </OpenFederationProvider>\n * ```\n */\nexport function OpenFederationProvider({ client, children }: OpenFederationProviderProps): React.ReactElement {\n const [user, setUser] = useState<User | null>(null);\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n let mounted = true;\n (async () => {\n const u = await client.getUser();\n if (!mounted) return;\n setUser(u);\n setReady(true);\n })();\n const unsubscribe = client.onAuthChange((u) => {\n if (mounted) setUser(u);\n });\n return () => {\n mounted = false;\n unsubscribe();\n };\n }, [client]);\n\n const value = useMemo(() => ({ client, user, ready }), [client, user, ready]);\n return (\n <OpenFederationContext.Provider value={value}>\n {children}\n </OpenFederationContext.Provider>\n );\n}\n\n/** Internal — use the public hooks from `./hooks.ts` instead. */\nexport function useOpenFederationContext(): OpenFederationContextValue {\n const ctx = useContext(OpenFederationContext);\n if (!ctx) {\n throw new Error(\n 'OpenFederation hooks/components must be used inside <OpenFederationProvider>. ' +\n 'Wrap your app root with it.'\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type {\n OpenFederationClient,\n User,\n LoginOptions,\n RegisterOptions,\n SignInWithOpenFederationOptions,\n SiwofAssertResponse,\n WalletLink,\n} from '@open-federation/sdk';\nimport { useOpenFederationContext } from './provider.js';\n\nexport type { User, WalletLink };\n\n/** Get the underlying OpenFederation SDK client directly. */\nexport function useOFClient(): OpenFederationClient {\n return useOpenFederationContext().client;\n}\n\nexport interface OFSession {\n user: User | null;\n ready: boolean;\n isAuthenticated: boolean;\n client: OpenFederationClient;\n login: (opts: LoginOptions) => Promise<User>;\n register: (opts: RegisterOptions) => Promise<User>;\n logout: () => Promise<void>;\n}\n\n/**\n * Primary hook for reading auth state and kicking off login / register /\n * logout. Re-renders automatically when the SDK's auth state changes.\n *\n * @example\n * ```tsx\n * const { user, login, logout, isAuthenticated } = useOFSession();\n * if (!isAuthenticated) return <LoginForm onSubmit={login} />;\n * return <div>Welcome, @{user.handle} <button onClick={logout}>Sign out</button></div>;\n * ```\n */\nexport function useOFSession(): OFSession {\n const { client, user, ready } = useOpenFederationContext();\n\n const login = useCallback((opts: LoginOptions) => client.login(opts), [client]);\n const register = useCallback((opts: RegisterOptions) => client.register(opts), [client]);\n const logout = useCallback(() => client.logout(), [client]);\n\n return {\n user,\n ready,\n isAuthenticated: user !== null,\n client,\n login,\n register,\n logout,\n };\n}\n\nexport interface OFWallets {\n wallets: WalletLink[];\n loading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n /**\n * Convenience wrapper for client.signInWithOpenFederation — runs the\n * full SIWOF flow and returns the assertion the dApp needs.\n */\n signIn: (opts: SignInWithOpenFederationOptions) => Promise<SiwofAssertResponse>;\n}\n\n/**\n * Hook for reading the current user's wallet portfolio and issuing SIWOF\n * assertions from React components.\n *\n * @example\n * ```tsx\n * const { wallets, signIn } = useOFWallet();\n *\n * async function handleSignIn() {\n * const eth = wallets.find(w => w.chain === 'ethereum');\n * if (!eth) return;\n * const assertion = await signIn({\n * chain: 'ethereum',\n * walletAddress: eth.walletAddress,\n * audience: window.location.origin,\n * });\n * // POST assertion.didToken + assertion.walletProof to your backend.\n * }\n * ```\n */\nexport function useOFWallet(): OFWallets {\n const { client, user } = useOpenFederationContext();\n const [wallets, setWallets] = useState<WalletLink[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!user) {\n setWallets([]);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n const res = await client.listWalletLinks();\n setWallets(res.walletLinks);\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [client, user]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n const signIn = useCallback(\n (opts: SignInWithOpenFederationOptions) => client.signInWithOpenFederation(opts),\n [client]\n );\n\n return { wallets, loading, error, refresh, signIn };\n}\n","import React, { useCallback, useState } from 'react';\nimport type {\n SiwofAssertResponse,\n WalletChain,\n} from '@open-federation/sdk';\nimport { useOpenFederationContext } from './provider.js';\n\nexport interface SignInWithOpenFederationProps {\n /** Chain to sign in with. */\n chain: WalletChain;\n /**\n * Wallet address to sign the SIWOF message with. If omitted, the user's\n * primary wallet for the chain is used (looked up via listWalletLinks).\n */\n walletAddress?: string;\n /**\n * Full audience URL (your dApp). Defaults to `window.location.origin`.\n */\n audience?: string;\n /** Optional prompt rendered inside the CAIP-122 message. */\n statement?: string;\n /** Callback invoked with the offline-verifiable assertion on success. */\n onSuccess: (assertion: SiwofAssertResponse) => void;\n /** Callback invoked with any error from the sign-in attempt. */\n onError?: (error: Error) => void;\n /** Customize the button label. Defaults to \"Sign in with OpenFederation\". */\n label?: React.ReactNode;\n /** Replace the default button with a render-prop for full control. */\n render?: (props: {\n onClick: () => void;\n loading: boolean;\n disabled: boolean;\n }) => React.ReactElement;\n /** Optional extra classes for the default button. */\n className?: string;\n /** Optional inline styles for the default button. */\n style?: React.CSSProperties;\n}\n\nconst DEFAULT_STYLE: React.CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '0.5rem',\n padding: '0.625rem 1rem',\n border: '1px solid rgba(0,0,0,0.1)',\n borderRadius: '0.5rem',\n background: '#111',\n color: '#fff',\n fontWeight: 600,\n cursor: 'pointer',\n};\n\n/**\n * Drop-in button that runs the full Sign-In With OpenFederation flow.\n *\n * @example\n * ```tsx\n * <SignInWithOpenFederation\n * chain=\"ethereum\"\n * audience={window.location.origin}\n * onSuccess={(assertion) => {\n * fetch('/api/login', { method: 'POST', body: JSON.stringify(assertion) });\n * }}\n * />\n * ```\n */\nexport function SignInWithOpenFederation(props: SignInWithOpenFederationProps): React.ReactElement {\n const { client, user } = useOpenFederationContext();\n const [loading, setLoading] = useState(false);\n\n const disabled = !user || loading;\n\n const handleClick = useCallback(async () => {\n if (disabled) return;\n setLoading(true);\n try {\n // Resolve wallet address if omitted.\n let walletAddress = props.walletAddress;\n if (!walletAddress) {\n const list = await client.listWalletLinks();\n const primaryOnChain = list.walletLinks.find(\n (w) => w.chain === props.chain\n );\n if (!primaryOnChain) {\n throw new Error(\n `No wallet found on chain \"${props.chain}\" for this account. Provision or link one first.`\n );\n }\n walletAddress = primaryOnChain.walletAddress;\n }\n\n const audience = props.audience\n ?? (typeof window !== 'undefined' ? window.location.origin : undefined);\n if (!audience) {\n throw new Error('audience is required (no window.location available)');\n }\n\n const assertion = await client.signInWithOpenFederation({\n chain: props.chain,\n walletAddress,\n audience,\n statement: props.statement,\n });\n props.onSuccess(assertion);\n } catch (err) {\n if (props.onError) props.onError(err as Error);\n else throw err;\n } finally {\n setLoading(false);\n }\n }, [client, disabled, props]);\n\n if (props.render) {\n return props.render({ onClick: handleClick, loading, disabled });\n }\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={disabled}\n className={props.className}\n style={{ ...DEFAULT_STYLE, opacity: disabled ? 0.6 : 1, ...(props.style ?? {}) }}\n data-openfederation-signin\n >\n {loading\n ? 'Signing…'\n : (props.label ?? 'Sign in with OpenFederation')}\n </button>\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { OpenFederationClient, User, LoginOptions, RegisterOptions, WalletLink, SignInWithOpenFederationOptions, SiwofAssertResponse, WalletChain } from '@open-federation/sdk';
|
|
3
|
+
|
|
4
|
+
interface OpenFederationProviderProps {
|
|
5
|
+
/** A client instance created via `createClient(...)` from @open-federation/sdk. */
|
|
6
|
+
client: OpenFederationClient;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap your React tree in this. The provider subscribes to the SDK's auth
|
|
11
|
+
* state and exposes the current user through `useOFSession()`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { createClient } from '@open-federation/sdk';
|
|
16
|
+
* import { OpenFederationProvider } from '@open-federation/react';
|
|
17
|
+
*
|
|
18
|
+
* const client = createClient({ serverUrl, partnerKey });
|
|
19
|
+
*
|
|
20
|
+
* <OpenFederationProvider client={client}>
|
|
21
|
+
* <App />
|
|
22
|
+
* </OpenFederationProvider>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function OpenFederationProvider({ client, children }: OpenFederationProviderProps): React.ReactElement;
|
|
26
|
+
|
|
27
|
+
/** Get the underlying OpenFederation SDK client directly. */
|
|
28
|
+
declare function useOFClient(): OpenFederationClient;
|
|
29
|
+
interface OFSession {
|
|
30
|
+
user: User | null;
|
|
31
|
+
ready: boolean;
|
|
32
|
+
isAuthenticated: boolean;
|
|
33
|
+
client: OpenFederationClient;
|
|
34
|
+
login: (opts: LoginOptions) => Promise<User>;
|
|
35
|
+
register: (opts: RegisterOptions) => Promise<User>;
|
|
36
|
+
logout: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Primary hook for reading auth state and kicking off login / register /
|
|
40
|
+
* logout. Re-renders automatically when the SDK's auth state changes.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const { user, login, logout, isAuthenticated } = useOFSession();
|
|
45
|
+
* if (!isAuthenticated) return <LoginForm onSubmit={login} />;
|
|
46
|
+
* return <div>Welcome, @{user.handle} <button onClick={logout}>Sign out</button></div>;
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function useOFSession(): OFSession;
|
|
50
|
+
interface OFWallets {
|
|
51
|
+
wallets: WalletLink[];
|
|
52
|
+
loading: boolean;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
refresh: () => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Convenience wrapper for client.signInWithOpenFederation — runs the
|
|
57
|
+
* full SIWOF flow and returns the assertion the dApp needs.
|
|
58
|
+
*/
|
|
59
|
+
signIn: (opts: SignInWithOpenFederationOptions) => Promise<SiwofAssertResponse>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Hook for reading the current user's wallet portfolio and issuing SIWOF
|
|
63
|
+
* assertions from React components.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const { wallets, signIn } = useOFWallet();
|
|
68
|
+
*
|
|
69
|
+
* async function handleSignIn() {
|
|
70
|
+
* const eth = wallets.find(w => w.chain === 'ethereum');
|
|
71
|
+
* if (!eth) return;
|
|
72
|
+
* const assertion = await signIn({
|
|
73
|
+
* chain: 'ethereum',
|
|
74
|
+
* walletAddress: eth.walletAddress,
|
|
75
|
+
* audience: window.location.origin,
|
|
76
|
+
* });
|
|
77
|
+
* // POST assertion.didToken + assertion.walletProof to your backend.
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function useOFWallet(): OFWallets;
|
|
82
|
+
|
|
83
|
+
interface SignInWithOpenFederationProps {
|
|
84
|
+
/** Chain to sign in with. */
|
|
85
|
+
chain: WalletChain;
|
|
86
|
+
/**
|
|
87
|
+
* Wallet address to sign the SIWOF message with. If omitted, the user's
|
|
88
|
+
* primary wallet for the chain is used (looked up via listWalletLinks).
|
|
89
|
+
*/
|
|
90
|
+
walletAddress?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Full audience URL (your dApp). Defaults to `window.location.origin`.
|
|
93
|
+
*/
|
|
94
|
+
audience?: string;
|
|
95
|
+
/** Optional prompt rendered inside the CAIP-122 message. */
|
|
96
|
+
statement?: string;
|
|
97
|
+
/** Callback invoked with the offline-verifiable assertion on success. */
|
|
98
|
+
onSuccess: (assertion: SiwofAssertResponse) => void;
|
|
99
|
+
/** Callback invoked with any error from the sign-in attempt. */
|
|
100
|
+
onError?: (error: Error) => void;
|
|
101
|
+
/** Customize the button label. Defaults to "Sign in with OpenFederation". */
|
|
102
|
+
label?: React.ReactNode;
|
|
103
|
+
/** Replace the default button with a render-prop for full control. */
|
|
104
|
+
render?: (props: {
|
|
105
|
+
onClick: () => void;
|
|
106
|
+
loading: boolean;
|
|
107
|
+
disabled: boolean;
|
|
108
|
+
}) => React.ReactElement;
|
|
109
|
+
/** Optional extra classes for the default button. */
|
|
110
|
+
className?: string;
|
|
111
|
+
/** Optional inline styles for the default button. */
|
|
112
|
+
style?: React.CSSProperties;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Drop-in button that runs the full Sign-In With OpenFederation flow.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```tsx
|
|
119
|
+
* <SignInWithOpenFederation
|
|
120
|
+
* chain="ethereum"
|
|
121
|
+
* audience={window.location.origin}
|
|
122
|
+
* onSuccess={(assertion) => {
|
|
123
|
+
* fetch('/api/login', { method: 'POST', body: JSON.stringify(assertion) });
|
|
124
|
+
* }}
|
|
125
|
+
* />
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function SignInWithOpenFederation(props: SignInWithOpenFederationProps): React.ReactElement;
|
|
129
|
+
|
|
130
|
+
export { type OFSession, type OFWallets, OpenFederationProvider, type OpenFederationProviderProps, SignInWithOpenFederation, type SignInWithOpenFederationProps, useOFClient, useOFSession, useOFWallet };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { OpenFederationClient, User, LoginOptions, RegisterOptions, WalletLink, SignInWithOpenFederationOptions, SiwofAssertResponse, WalletChain } from '@open-federation/sdk';
|
|
3
|
+
|
|
4
|
+
interface OpenFederationProviderProps {
|
|
5
|
+
/** A client instance created via `createClient(...)` from @open-federation/sdk. */
|
|
6
|
+
client: OpenFederationClient;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Wrap your React tree in this. The provider subscribes to the SDK's auth
|
|
11
|
+
* state and exposes the current user through `useOFSession()`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { createClient } from '@open-federation/sdk';
|
|
16
|
+
* import { OpenFederationProvider } from '@open-federation/react';
|
|
17
|
+
*
|
|
18
|
+
* const client = createClient({ serverUrl, partnerKey });
|
|
19
|
+
*
|
|
20
|
+
* <OpenFederationProvider client={client}>
|
|
21
|
+
* <App />
|
|
22
|
+
* </OpenFederationProvider>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function OpenFederationProvider({ client, children }: OpenFederationProviderProps): React.ReactElement;
|
|
26
|
+
|
|
27
|
+
/** Get the underlying OpenFederation SDK client directly. */
|
|
28
|
+
declare function useOFClient(): OpenFederationClient;
|
|
29
|
+
interface OFSession {
|
|
30
|
+
user: User | null;
|
|
31
|
+
ready: boolean;
|
|
32
|
+
isAuthenticated: boolean;
|
|
33
|
+
client: OpenFederationClient;
|
|
34
|
+
login: (opts: LoginOptions) => Promise<User>;
|
|
35
|
+
register: (opts: RegisterOptions) => Promise<User>;
|
|
36
|
+
logout: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Primary hook for reading auth state and kicking off login / register /
|
|
40
|
+
* logout. Re-renders automatically when the SDK's auth state changes.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const { user, login, logout, isAuthenticated } = useOFSession();
|
|
45
|
+
* if (!isAuthenticated) return <LoginForm onSubmit={login} />;
|
|
46
|
+
* return <div>Welcome, @{user.handle} <button onClick={logout}>Sign out</button></div>;
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function useOFSession(): OFSession;
|
|
50
|
+
interface OFWallets {
|
|
51
|
+
wallets: WalletLink[];
|
|
52
|
+
loading: boolean;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
refresh: () => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Convenience wrapper for client.signInWithOpenFederation — runs the
|
|
57
|
+
* full SIWOF flow and returns the assertion the dApp needs.
|
|
58
|
+
*/
|
|
59
|
+
signIn: (opts: SignInWithOpenFederationOptions) => Promise<SiwofAssertResponse>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Hook for reading the current user's wallet portfolio and issuing SIWOF
|
|
63
|
+
* assertions from React components.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const { wallets, signIn } = useOFWallet();
|
|
68
|
+
*
|
|
69
|
+
* async function handleSignIn() {
|
|
70
|
+
* const eth = wallets.find(w => w.chain === 'ethereum');
|
|
71
|
+
* if (!eth) return;
|
|
72
|
+
* const assertion = await signIn({
|
|
73
|
+
* chain: 'ethereum',
|
|
74
|
+
* walletAddress: eth.walletAddress,
|
|
75
|
+
* audience: window.location.origin,
|
|
76
|
+
* });
|
|
77
|
+
* // POST assertion.didToken + assertion.walletProof to your backend.
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function useOFWallet(): OFWallets;
|
|
82
|
+
|
|
83
|
+
interface SignInWithOpenFederationProps {
|
|
84
|
+
/** Chain to sign in with. */
|
|
85
|
+
chain: WalletChain;
|
|
86
|
+
/**
|
|
87
|
+
* Wallet address to sign the SIWOF message with. If omitted, the user's
|
|
88
|
+
* primary wallet for the chain is used (looked up via listWalletLinks).
|
|
89
|
+
*/
|
|
90
|
+
walletAddress?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Full audience URL (your dApp). Defaults to `window.location.origin`.
|
|
93
|
+
*/
|
|
94
|
+
audience?: string;
|
|
95
|
+
/** Optional prompt rendered inside the CAIP-122 message. */
|
|
96
|
+
statement?: string;
|
|
97
|
+
/** Callback invoked with the offline-verifiable assertion on success. */
|
|
98
|
+
onSuccess: (assertion: SiwofAssertResponse) => void;
|
|
99
|
+
/** Callback invoked with any error from the sign-in attempt. */
|
|
100
|
+
onError?: (error: Error) => void;
|
|
101
|
+
/** Customize the button label. Defaults to "Sign in with OpenFederation". */
|
|
102
|
+
label?: React.ReactNode;
|
|
103
|
+
/** Replace the default button with a render-prop for full control. */
|
|
104
|
+
render?: (props: {
|
|
105
|
+
onClick: () => void;
|
|
106
|
+
loading: boolean;
|
|
107
|
+
disabled: boolean;
|
|
108
|
+
}) => React.ReactElement;
|
|
109
|
+
/** Optional extra classes for the default button. */
|
|
110
|
+
className?: string;
|
|
111
|
+
/** Optional inline styles for the default button. */
|
|
112
|
+
style?: React.CSSProperties;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Drop-in button that runs the full Sign-In With OpenFederation flow.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```tsx
|
|
119
|
+
* <SignInWithOpenFederation
|
|
120
|
+
* chain="ethereum"
|
|
121
|
+
* audience={window.location.origin}
|
|
122
|
+
* onSuccess={(assertion) => {
|
|
123
|
+
* fetch('/api/login', { method: 'POST', body: JSON.stringify(assertion) });
|
|
124
|
+
* }}
|
|
125
|
+
* />
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function SignInWithOpenFederation(props: SignInWithOpenFederationProps): React.ReactElement;
|
|
129
|
+
|
|
130
|
+
export { type OFSession, type OFWallets, OpenFederationProvider, type OpenFederationProviderProps, SignInWithOpenFederation, type SignInWithOpenFederationProps, useOFClient, useOFSession, useOFWallet };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import { createContext, useContext, useEffect, useState, useMemo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var OpenFederationContext = createContext(null);
|
|
5
|
+
function OpenFederationProvider({ client, children }) {
|
|
6
|
+
const [user, setUser] = useState(null);
|
|
7
|
+
const [ready, setReady] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
let mounted = true;
|
|
10
|
+
(async () => {
|
|
11
|
+
const u = await client.getUser();
|
|
12
|
+
if (!mounted) return;
|
|
13
|
+
setUser(u);
|
|
14
|
+
setReady(true);
|
|
15
|
+
})();
|
|
16
|
+
const unsubscribe = client.onAuthChange((u) => {
|
|
17
|
+
if (mounted) setUser(u);
|
|
18
|
+
});
|
|
19
|
+
return () => {
|
|
20
|
+
mounted = false;
|
|
21
|
+
unsubscribe();
|
|
22
|
+
};
|
|
23
|
+
}, [client]);
|
|
24
|
+
const value = useMemo(() => ({ client, user, ready }), [client, user, ready]);
|
|
25
|
+
return /* @__PURE__ */ jsx(OpenFederationContext.Provider, { value, children });
|
|
26
|
+
}
|
|
27
|
+
function useOpenFederationContext() {
|
|
28
|
+
const ctx = useContext(OpenFederationContext);
|
|
29
|
+
if (!ctx) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"OpenFederation hooks/components must be used inside <OpenFederationProvider>. Wrap your app root with it."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return ctx;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/hooks.ts
|
|
38
|
+
import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
|
|
39
|
+
function useOFClient() {
|
|
40
|
+
return useOpenFederationContext().client;
|
|
41
|
+
}
|
|
42
|
+
function useOFSession() {
|
|
43
|
+
const { client, user, ready } = useOpenFederationContext();
|
|
44
|
+
const login = useCallback((opts) => client.login(opts), [client]);
|
|
45
|
+
const register = useCallback((opts) => client.register(opts), [client]);
|
|
46
|
+
const logout = useCallback(() => client.logout(), [client]);
|
|
47
|
+
return {
|
|
48
|
+
user,
|
|
49
|
+
ready,
|
|
50
|
+
isAuthenticated: user !== null,
|
|
51
|
+
client,
|
|
52
|
+
login,
|
|
53
|
+
register,
|
|
54
|
+
logout
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function useOFWallet() {
|
|
58
|
+
const { client, user } = useOpenFederationContext();
|
|
59
|
+
const [wallets, setWallets] = useState2([]);
|
|
60
|
+
const [loading, setLoading] = useState2(false);
|
|
61
|
+
const [error, setError] = useState2(null);
|
|
62
|
+
const refresh = useCallback(async () => {
|
|
63
|
+
if (!user) {
|
|
64
|
+
setWallets([]);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setLoading(true);
|
|
68
|
+
setError(null);
|
|
69
|
+
try {
|
|
70
|
+
const res = await client.listWalletLinks();
|
|
71
|
+
setWallets(res.walletLinks);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setError(err);
|
|
74
|
+
} finally {
|
|
75
|
+
setLoading(false);
|
|
76
|
+
}
|
|
77
|
+
}, [client, user]);
|
|
78
|
+
useEffect2(() => {
|
|
79
|
+
refresh();
|
|
80
|
+
}, [refresh]);
|
|
81
|
+
const signIn = useCallback(
|
|
82
|
+
(opts) => client.signInWithOpenFederation(opts),
|
|
83
|
+
[client]
|
|
84
|
+
);
|
|
85
|
+
return { wallets, loading, error, refresh, signIn };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/sign-in-button.tsx
|
|
89
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
90
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
91
|
+
var DEFAULT_STYLE = {
|
|
92
|
+
display: "inline-flex",
|
|
93
|
+
alignItems: "center",
|
|
94
|
+
justifyContent: "center",
|
|
95
|
+
gap: "0.5rem",
|
|
96
|
+
padding: "0.625rem 1rem",
|
|
97
|
+
border: "1px solid rgba(0,0,0,0.1)",
|
|
98
|
+
borderRadius: "0.5rem",
|
|
99
|
+
background: "#111",
|
|
100
|
+
color: "#fff",
|
|
101
|
+
fontWeight: 600,
|
|
102
|
+
cursor: "pointer"
|
|
103
|
+
};
|
|
104
|
+
function SignInWithOpenFederation(props) {
|
|
105
|
+
const { client, user } = useOpenFederationContext();
|
|
106
|
+
const [loading, setLoading] = useState3(false);
|
|
107
|
+
const disabled = !user || loading;
|
|
108
|
+
const handleClick = useCallback2(async () => {
|
|
109
|
+
if (disabled) return;
|
|
110
|
+
setLoading(true);
|
|
111
|
+
try {
|
|
112
|
+
let walletAddress = props.walletAddress;
|
|
113
|
+
if (!walletAddress) {
|
|
114
|
+
const list = await client.listWalletLinks();
|
|
115
|
+
const primaryOnChain = list.walletLinks.find(
|
|
116
|
+
(w) => w.chain === props.chain
|
|
117
|
+
);
|
|
118
|
+
if (!primaryOnChain) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`No wallet found on chain "${props.chain}" for this account. Provision or link one first.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
walletAddress = primaryOnChain.walletAddress;
|
|
124
|
+
}
|
|
125
|
+
const audience = props.audience ?? (typeof window !== "undefined" ? window.location.origin : void 0);
|
|
126
|
+
if (!audience) {
|
|
127
|
+
throw new Error("audience is required (no window.location available)");
|
|
128
|
+
}
|
|
129
|
+
const assertion = await client.signInWithOpenFederation({
|
|
130
|
+
chain: props.chain,
|
|
131
|
+
walletAddress,
|
|
132
|
+
audience,
|
|
133
|
+
statement: props.statement
|
|
134
|
+
});
|
|
135
|
+
props.onSuccess(assertion);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (props.onError) props.onError(err);
|
|
138
|
+
else throw err;
|
|
139
|
+
} finally {
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}
|
|
142
|
+
}, [client, disabled, props]);
|
|
143
|
+
if (props.render) {
|
|
144
|
+
return props.render({ onClick: handleClick, loading, disabled });
|
|
145
|
+
}
|
|
146
|
+
return /* @__PURE__ */ jsx2(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
type: "button",
|
|
150
|
+
onClick: handleClick,
|
|
151
|
+
disabled,
|
|
152
|
+
className: props.className,
|
|
153
|
+
style: { ...DEFAULT_STYLE, opacity: disabled ? 0.6 : 1, ...props.style ?? {} },
|
|
154
|
+
"data-openfederation-signin": true,
|
|
155
|
+
children: loading ? "Signing\u2026" : props.label ?? "Sign in with OpenFederation"
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
OpenFederationProvider,
|
|
161
|
+
SignInWithOpenFederation,
|
|
162
|
+
useOFClient,
|
|
163
|
+
useOFSession,
|
|
164
|
+
useOFWallet
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../src/sign-in-button.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';\nimport type { OpenFederationClient, User } from '@open-federation/sdk';\n\nexport interface OpenFederationProviderProps {\n /** A client instance created via `createClient(...)` from @open-federation/sdk. */\n client: OpenFederationClient;\n children: React.ReactNode;\n}\n\ninterface OpenFederationContextValue {\n client: OpenFederationClient;\n user: User | null;\n ready: boolean;\n}\n\nconst OpenFederationContext = createContext<OpenFederationContextValue | null>(null);\n\n/**\n * Wrap your React tree in this. The provider subscribes to the SDK's auth\n * state and exposes the current user through `useOFSession()`.\n *\n * @example\n * ```tsx\n * import { createClient } from '@open-federation/sdk';\n * import { OpenFederationProvider } from '@open-federation/react';\n *\n * const client = createClient({ serverUrl, partnerKey });\n *\n * <OpenFederationProvider client={client}>\n * <App />\n * </OpenFederationProvider>\n * ```\n */\nexport function OpenFederationProvider({ client, children }: OpenFederationProviderProps): React.ReactElement {\n const [user, setUser] = useState<User | null>(null);\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n let mounted = true;\n (async () => {\n const u = await client.getUser();\n if (!mounted) return;\n setUser(u);\n setReady(true);\n })();\n const unsubscribe = client.onAuthChange((u) => {\n if (mounted) setUser(u);\n });\n return () => {\n mounted = false;\n unsubscribe();\n };\n }, [client]);\n\n const value = useMemo(() => ({ client, user, ready }), [client, user, ready]);\n return (\n <OpenFederationContext.Provider value={value}>\n {children}\n </OpenFederationContext.Provider>\n );\n}\n\n/** Internal — use the public hooks from `./hooks.ts` instead. */\nexport function useOpenFederationContext(): OpenFederationContextValue {\n const ctx = useContext(OpenFederationContext);\n if (!ctx) {\n throw new Error(\n 'OpenFederation hooks/components must be used inside <OpenFederationProvider>. ' +\n 'Wrap your app root with it.'\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type {\n OpenFederationClient,\n User,\n LoginOptions,\n RegisterOptions,\n SignInWithOpenFederationOptions,\n SiwofAssertResponse,\n WalletLink,\n} from '@open-federation/sdk';\nimport { useOpenFederationContext } from './provider.js';\n\nexport type { User, WalletLink };\n\n/** Get the underlying OpenFederation SDK client directly. */\nexport function useOFClient(): OpenFederationClient {\n return useOpenFederationContext().client;\n}\n\nexport interface OFSession {\n user: User | null;\n ready: boolean;\n isAuthenticated: boolean;\n client: OpenFederationClient;\n login: (opts: LoginOptions) => Promise<User>;\n register: (opts: RegisterOptions) => Promise<User>;\n logout: () => Promise<void>;\n}\n\n/**\n * Primary hook for reading auth state and kicking off login / register /\n * logout. Re-renders automatically when the SDK's auth state changes.\n *\n * @example\n * ```tsx\n * const { user, login, logout, isAuthenticated } = useOFSession();\n * if (!isAuthenticated) return <LoginForm onSubmit={login} />;\n * return <div>Welcome, @{user.handle} <button onClick={logout}>Sign out</button></div>;\n * ```\n */\nexport function useOFSession(): OFSession {\n const { client, user, ready } = useOpenFederationContext();\n\n const login = useCallback((opts: LoginOptions) => client.login(opts), [client]);\n const register = useCallback((opts: RegisterOptions) => client.register(opts), [client]);\n const logout = useCallback(() => client.logout(), [client]);\n\n return {\n user,\n ready,\n isAuthenticated: user !== null,\n client,\n login,\n register,\n logout,\n };\n}\n\nexport interface OFWallets {\n wallets: WalletLink[];\n loading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n /**\n * Convenience wrapper for client.signInWithOpenFederation — runs the\n * full SIWOF flow and returns the assertion the dApp needs.\n */\n signIn: (opts: SignInWithOpenFederationOptions) => Promise<SiwofAssertResponse>;\n}\n\n/**\n * Hook for reading the current user's wallet portfolio and issuing SIWOF\n * assertions from React components.\n *\n * @example\n * ```tsx\n * const { wallets, signIn } = useOFWallet();\n *\n * async function handleSignIn() {\n * const eth = wallets.find(w => w.chain === 'ethereum');\n * if (!eth) return;\n * const assertion = await signIn({\n * chain: 'ethereum',\n * walletAddress: eth.walletAddress,\n * audience: window.location.origin,\n * });\n * // POST assertion.didToken + assertion.walletProof to your backend.\n * }\n * ```\n */\nexport function useOFWallet(): OFWallets {\n const { client, user } = useOpenFederationContext();\n const [wallets, setWallets] = useState<WalletLink[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!user) {\n setWallets([]);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n const res = await client.listWalletLinks();\n setWallets(res.walletLinks);\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [client, user]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n const signIn = useCallback(\n (opts: SignInWithOpenFederationOptions) => client.signInWithOpenFederation(opts),\n [client]\n );\n\n return { wallets, loading, error, refresh, signIn };\n}\n","import React, { useCallback, useState } from 'react';\nimport type {\n SiwofAssertResponse,\n WalletChain,\n} from '@open-federation/sdk';\nimport { useOpenFederationContext } from './provider.js';\n\nexport interface SignInWithOpenFederationProps {\n /** Chain to sign in with. */\n chain: WalletChain;\n /**\n * Wallet address to sign the SIWOF message with. If omitted, the user's\n * primary wallet for the chain is used (looked up via listWalletLinks).\n */\n walletAddress?: string;\n /**\n * Full audience URL (your dApp). Defaults to `window.location.origin`.\n */\n audience?: string;\n /** Optional prompt rendered inside the CAIP-122 message. */\n statement?: string;\n /** Callback invoked with the offline-verifiable assertion on success. */\n onSuccess: (assertion: SiwofAssertResponse) => void;\n /** Callback invoked with any error from the sign-in attempt. */\n onError?: (error: Error) => void;\n /** Customize the button label. Defaults to \"Sign in with OpenFederation\". */\n label?: React.ReactNode;\n /** Replace the default button with a render-prop for full control. */\n render?: (props: {\n onClick: () => void;\n loading: boolean;\n disabled: boolean;\n }) => React.ReactElement;\n /** Optional extra classes for the default button. */\n className?: string;\n /** Optional inline styles for the default button. */\n style?: React.CSSProperties;\n}\n\nconst DEFAULT_STYLE: React.CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '0.5rem',\n padding: '0.625rem 1rem',\n border: '1px solid rgba(0,0,0,0.1)',\n borderRadius: '0.5rem',\n background: '#111',\n color: '#fff',\n fontWeight: 600,\n cursor: 'pointer',\n};\n\n/**\n * Drop-in button that runs the full Sign-In With OpenFederation flow.\n *\n * @example\n * ```tsx\n * <SignInWithOpenFederation\n * chain=\"ethereum\"\n * audience={window.location.origin}\n * onSuccess={(assertion) => {\n * fetch('/api/login', { method: 'POST', body: JSON.stringify(assertion) });\n * }}\n * />\n * ```\n */\nexport function SignInWithOpenFederation(props: SignInWithOpenFederationProps): React.ReactElement {\n const { client, user } = useOpenFederationContext();\n const [loading, setLoading] = useState(false);\n\n const disabled = !user || loading;\n\n const handleClick = useCallback(async () => {\n if (disabled) return;\n setLoading(true);\n try {\n // Resolve wallet address if omitted.\n let walletAddress = props.walletAddress;\n if (!walletAddress) {\n const list = await client.listWalletLinks();\n const primaryOnChain = list.walletLinks.find(\n (w) => w.chain === props.chain\n );\n if (!primaryOnChain) {\n throw new Error(\n `No wallet found on chain \"${props.chain}\" for this account. Provision or link one first.`\n );\n }\n walletAddress = primaryOnChain.walletAddress;\n }\n\n const audience = props.audience\n ?? (typeof window !== 'undefined' ? window.location.origin : undefined);\n if (!audience) {\n throw new Error('audience is required (no window.location available)');\n }\n\n const assertion = await client.signInWithOpenFederation({\n chain: props.chain,\n walletAddress,\n audience,\n statement: props.statement,\n });\n props.onSuccess(assertion);\n } catch (err) {\n if (props.onError) props.onError(err as Error);\n else throw err;\n } finally {\n setLoading(false);\n }\n }, [client, disabled, props]);\n\n if (props.render) {\n return props.render({ onClick: handleClick, loading, disabled });\n }\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={disabled}\n className={props.className}\n style={{ ...DEFAULT_STYLE, opacity: disabled ? 0.6 : 1, ...(props.style ?? {}) }}\n data-openfederation-signin\n >\n {loading\n ? 'Signing…'\n : (props.label ?? 'Sign in with OpenFederation')}\n </button>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useCallback","useState","jsx","useState","useCallback"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-federation/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React bindings for @open-federation/sdk — provider, hooks, and a drop-in Sign In With OpenFederation button.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@open-federation/sdk": "^0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=18.0.0",
|
|
33
|
+
"react-dom": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^18.0.0",
|
|
37
|
+
"@types/react-dom": "^18.0.0",
|
|
38
|
+
"happy-dom": "^20.9.0",
|
|
39
|
+
"react": "^18.0.0",
|
|
40
|
+
"react-dom": "^18.0.0",
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.4.0"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"openfederation",
|
|
46
|
+
"react",
|
|
47
|
+
"web3",
|
|
48
|
+
"did",
|
|
49
|
+
"sign-in"
|
|
50
|
+
],
|
|
51
|
+
"license": "MIT OR Apache-2.0",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/OpenFederationPDS/OpenFederationPDS"
|
|
55
|
+
}
|
|
56
|
+
}
|