authfyio-react 0.3.9
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/README.md +83 -0
- package/dist/client.d.ts +128 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +227 -0
- package/dist/components.d.ts +79 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +111 -0
- package/dist/hooks-flow.d.ts +59 -0
- package/dist/hooks-flow.d.ts.map +1 -0
- package/dist/hooks-flow.js +133 -0
- package/dist/hooks.d.ts +113 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +212 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/jwt.d.ts +20 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +30 -0
- package/dist/provider.d.ts +50 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +91 -0
- package/dist/ui/CheckoutButton.d.ts +14 -0
- package/dist/ui/CheckoutButton.d.ts.map +1 -0
- package/dist/ui/CheckoutButton.js +28 -0
- package/dist/ui/Control.d.ts +17 -0
- package/dist/ui/Control.d.ts.map +1 -0
- package/dist/ui/Control.js +33 -0
- package/dist/ui/CreateOrganization.d.ts +19 -0
- package/dist/ui/CreateOrganization.d.ts.map +1 -0
- package/dist/ui/CreateOrganization.js +75 -0
- package/dist/ui/OrganizationList.d.ts +13 -0
- package/dist/ui/OrganizationList.d.ts.map +1 -0
- package/dist/ui/OrganizationList.js +47 -0
- package/dist/ui/OrganizationProfile.d.ts +12 -0
- package/dist/ui/OrganizationProfile.d.ts.map +1 -0
- package/dist/ui/OrganizationProfile.js +116 -0
- package/dist/ui/OrganizationSwitcher.d.ts +17 -0
- package/dist/ui/OrganizationSwitcher.d.ts.map +1 -0
- package/dist/ui/OrganizationSwitcher.js +59 -0
- package/dist/ui/PricingTable.d.ts +15 -0
- package/dist/ui/PricingTable.d.ts.map +1 -0
- package/dist/ui/PricingTable.js +74 -0
- package/dist/ui/SignIn.d.ts +23 -0
- package/dist/ui/SignIn.d.ts.map +1 -0
- package/dist/ui/SignIn.js +489 -0
- package/dist/ui/SignUp.d.ts +18 -0
- package/dist/ui/SignUp.d.ts.map +1 -0
- package/dist/ui/SignUp.js +153 -0
- package/dist/ui/UnstyledButtons.d.ts +24 -0
- package/dist/ui/UnstyledButtons.d.ts.map +1 -0
- package/dist/ui/UnstyledButtons.js +42 -0
- package/dist/ui/UserAvatar.d.ts +14 -0
- package/dist/ui/UserAvatar.d.ts.map +1 -0
- package/dist/ui/UserAvatar.js +52 -0
- package/dist/ui/UserButton.d.ts +15 -0
- package/dist/ui/UserButton.d.ts.map +1 -0
- package/dist/ui/UserButton.js +82 -0
- package/dist/ui/UserProfile.d.ts +19 -0
- package/dist/ui/UserProfile.d.ts.map +1 -0
- package/dist/ui/UserProfile.js +199 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +13 -0
- package/dist/ui/styles.d.ts +10 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/styles.js +291 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# authfyio-react
|
|
2
|
+
|
|
3
|
+
React SDK for Authfyio. Provides a `<AuthfyioProvider />`, hooks for session state, and a background refresh loop that keeps the short-lived `__session` JWT valid without interrupting your UI.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install authfyio-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires React 18 or newer.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// app.tsx
|
|
17
|
+
import { AuthfyioProvider } from 'authfyio-react';
|
|
18
|
+
|
|
19
|
+
export default function App({ children }: { children: React.ReactNode }) {
|
|
20
|
+
return (
|
|
21
|
+
<AuthfyioProvider baseUrl={process.env.REACT_APP_AF_API_URL!}>
|
|
22
|
+
{children}
|
|
23
|
+
</AuthfyioProvider>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// any component
|
|
30
|
+
import { useSession, useUserId } from 'authfyio-react';
|
|
31
|
+
|
|
32
|
+
export function Avatar() {
|
|
33
|
+
const session = useSession();
|
|
34
|
+
const userId = useUserId();
|
|
35
|
+
|
|
36
|
+
if (session.status === 'signed_out') return <SignInButton />;
|
|
37
|
+
return <span>Signed in as {userId}</span>;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### `<AuthfyioProvider />`
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Default | Description |
|
|
46
|
+
| --------------------- | --------- | ------- | ---------------------------------------------------------------------------- |
|
|
47
|
+
| `baseUrl` | `string` | — | Instance API base URL (e.g. `https://auth.example.com`). |
|
|
48
|
+
| `autoRefresh` | `boolean` | `true` | Runs the refresh loop in the background to keep `__session` fresh. |
|
|
49
|
+
| `refreshSkewSeconds` | `number` | `15` | How many seconds before JWT expiry to trigger a refresh. |
|
|
50
|
+
|
|
51
|
+
The provider reads the `__session` cookie on mount, decodes its claims, and exposes them to descendants. When `autoRefresh` is on, it schedules a refresh that fires `refreshSkewSeconds` before `exp` and repeats for the lifetime of the session.
|
|
52
|
+
|
|
53
|
+
### Hooks
|
|
54
|
+
|
|
55
|
+
- `useSession()` — returns `{ status: 'signed_in', jwt, claims } | { status: 'signed_out' }`.
|
|
56
|
+
- `useUserId()` — returns the `sub` claim when signed in, otherwise `null`.
|
|
57
|
+
- `useAuthfyio()` — returns `{ client, session, refresh }`. `refresh()` lets you trigger a token refresh manually (e.g. after a password change).
|
|
58
|
+
|
|
59
|
+
### Low-level client
|
|
60
|
+
|
|
61
|
+
`AuthfyioReactClient` is exposed if you want to skip the provider — useful for scripts or non-React roots. Most apps should not need it.
|
|
62
|
+
|
|
63
|
+
## How the refresh loop works
|
|
64
|
+
|
|
65
|
+
Your instance issues two cookies:
|
|
66
|
+
|
|
67
|
+
- `__client` (httpOnly, long-lived) — held by the browser, used only for the refresh call.
|
|
68
|
+
- `__session` (readable by JS, ~60s) — the JWT your frontend sends to your backend.
|
|
69
|
+
|
|
70
|
+
The SDK decodes `__session.exp`, schedules `setTimeout(refresh, exp - skew)`, hits the instance's `/v1/auth/refresh` endpoint, and re-reads the new `__session` cookie. The httpOnly `__client` never touches JS memory. If the refresh fails (revoked, network error), state transitions to `signed_out` and children re-render.
|
|
71
|
+
|
|
72
|
+
## Next.js + Server Components
|
|
73
|
+
|
|
74
|
+
This package is a **Client SDK** (`'use client'` under the hood). For App Router middleware and server-side session access, use `authfyio-nextjs`.
|
|
75
|
+
|
|
76
|
+
## Error handling
|
|
77
|
+
|
|
78
|
+
- `useSession()` never throws — it simply reports `signed_out` when the cookie is missing or invalid.
|
|
79
|
+
- Manual `refresh()` can throw network errors; wrap it in a `try/catch` if you surface retry UI.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type SessionClaims } from './jwt.js';
|
|
2
|
+
export declare const PUBLISHABLE_KEY_HEADER = "x-authfyio-publishable-key";
|
|
3
|
+
export type AuthConfigSnapshot = {
|
|
4
|
+
signIn: {
|
|
5
|
+
emailPassword: boolean;
|
|
6
|
+
usernamePassword: boolean;
|
|
7
|
+
magicLink: boolean;
|
|
8
|
+
emailCode: boolean;
|
|
9
|
+
phoneOtp: boolean;
|
|
10
|
+
passkey: boolean;
|
|
11
|
+
};
|
|
12
|
+
signUp: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
email: boolean;
|
|
15
|
+
emailRequired: boolean;
|
|
16
|
+
username: {
|
|
17
|
+
required: boolean;
|
|
18
|
+
minLength: number;
|
|
19
|
+
maxLength: number;
|
|
20
|
+
} | null;
|
|
21
|
+
password: boolean;
|
|
22
|
+
phone: boolean;
|
|
23
|
+
firstAndLastName: {
|
|
24
|
+
required: boolean;
|
|
25
|
+
} | null;
|
|
26
|
+
verifyAtSignUp: boolean;
|
|
27
|
+
};
|
|
28
|
+
socialProviders: string[];
|
|
29
|
+
web3Wallets: string[];
|
|
30
|
+
legal: {
|
|
31
|
+
termsOfServiceUrl: string | null;
|
|
32
|
+
privacyPolicyUrl: string | null;
|
|
33
|
+
requireConsent: boolean;
|
|
34
|
+
};
|
|
35
|
+
branding: {
|
|
36
|
+
primaryColor: string | null;
|
|
37
|
+
logoUrl: string | null;
|
|
38
|
+
appName: string | null;
|
|
39
|
+
removeBranding: boolean;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export type AuthfyioReactClientOptions = {
|
|
43
|
+
baseUrl: string;
|
|
44
|
+
/**
|
|
45
|
+
* `pk_live_…` / `pk_test_…` — identifies which environment to route
|
|
46
|
+
* to on api.authfyio.com. Required when calling the hosted SaaS so
|
|
47
|
+
* requests don't depend on container-level `AF_ENVIRONMENT_ID`.
|
|
48
|
+
*/
|
|
49
|
+
publishableKey?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Minimal browser-side client used by the React hooks. Mirrors the resource
|
|
53
|
+
* shapes defined in hooks.ts so the type-checker is happy. All methods use
|
|
54
|
+
* `credentials: 'include'` so the FAPI session cookie travels with the request.
|
|
55
|
+
*/
|
|
56
|
+
export declare class AuthfyioReactClient {
|
|
57
|
+
readonly baseUrl: string;
|
|
58
|
+
readonly publishableKey: string | null;
|
|
59
|
+
constructor(opts: AuthfyioReactClientOptions);
|
|
60
|
+
private headers;
|
|
61
|
+
/**
|
|
62
|
+
* Public auth-method config for this instance — which methods, social
|
|
63
|
+
* providers and web3 wallets the dashboard has enabled. Safe to call
|
|
64
|
+
* unauthenticated.
|
|
65
|
+
*/
|
|
66
|
+
fetchAuthConfig(): Promise<AuthConfigSnapshot | null>;
|
|
67
|
+
getSessionFromCookies(): {
|
|
68
|
+
jwt: string;
|
|
69
|
+
claims: SessionClaims;
|
|
70
|
+
} | null;
|
|
71
|
+
refresh(): Promise<{
|
|
72
|
+
ok: true;
|
|
73
|
+
jwt: string;
|
|
74
|
+
claims: SessionClaims;
|
|
75
|
+
} | {
|
|
76
|
+
ok: false;
|
|
77
|
+
}>;
|
|
78
|
+
fetchCurrentUser(): Promise<{
|
|
79
|
+
id: string;
|
|
80
|
+
email: string | null;
|
|
81
|
+
username: string | null;
|
|
82
|
+
firstName: string | null;
|
|
83
|
+
lastName: string | null;
|
|
84
|
+
publicMetadata: Record<string, unknown>;
|
|
85
|
+
createdAt: string;
|
|
86
|
+
} | null>;
|
|
87
|
+
fetchOrganization(orgId: string): Promise<{
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
slug: string;
|
|
91
|
+
} | null>;
|
|
92
|
+
fetchPlans(): Promise<Array<{
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
description: string | null;
|
|
96
|
+
monthlyPriceCents: number;
|
|
97
|
+
annualPriceCents: number | null;
|
|
98
|
+
}>>;
|
|
99
|
+
/**
|
|
100
|
+
* Returns the caller's effective plan + attached features, used by
|
|
101
|
+
* `useAuth().has({ plan })` / `has({ feature })` and `<Protect>` /
|
|
102
|
+
* `<Show when>`. Always served by the instance API; returns `null` for
|
|
103
|
+
* signed-out users.
|
|
104
|
+
*/
|
|
105
|
+
fetchMyBilling(): Promise<{
|
|
106
|
+
plan: {
|
|
107
|
+
id: string;
|
|
108
|
+
key: string | null;
|
|
109
|
+
name: string;
|
|
110
|
+
} | null;
|
|
111
|
+
features: Array<{
|
|
112
|
+
key: string;
|
|
113
|
+
name: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
}>;
|
|
116
|
+
status: string | null;
|
|
117
|
+
} | null>;
|
|
118
|
+
fetchSubscription(): Promise<{
|
|
119
|
+
id: string;
|
|
120
|
+
status: string;
|
|
121
|
+
planId: string;
|
|
122
|
+
currentPeriodEnd: string | null;
|
|
123
|
+
} | null>;
|
|
124
|
+
createCheckoutSession(planId: string, billingCycle?: 'monthly' | 'annual'): Promise<{
|
|
125
|
+
url: string;
|
|
126
|
+
}>;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { decodeJwtPayload, getSessionJwtFromDocumentCookie } from './jwt.js';
|
|
2
|
+
export const PUBLISHABLE_KEY_HEADER = 'x-authfyio-publishable-key';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal browser-side client used by the React hooks. Mirrors the resource
|
|
5
|
+
* shapes defined in hooks.ts so the type-checker is happy. All methods use
|
|
6
|
+
* `credentials: 'include'` so the FAPI session cookie travels with the request.
|
|
7
|
+
*/
|
|
8
|
+
export class AuthfyioReactClient {
|
|
9
|
+
// Public so UI components (notably <SignIn>) can append the
|
|
10
|
+
// publishable key to OAuth /authorize anchors as a query string.
|
|
11
|
+
// Top-level browser navigations don't carry custom headers, so the
|
|
12
|
+
// header path used by fetch() requests doesn't apply here.
|
|
13
|
+
baseUrl;
|
|
14
|
+
publishableKey;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
|
|
17
|
+
this.publishableKey = opts.publishableKey ?? null;
|
|
18
|
+
}
|
|
19
|
+
headers(extra = {}) {
|
|
20
|
+
const out = { ...extra };
|
|
21
|
+
if (this.publishableKey)
|
|
22
|
+
out[PUBLISHABLE_KEY_HEADER] = this.publishableKey;
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Public auth-method config for this instance — which methods, social
|
|
27
|
+
* providers and web3 wallets the dashboard has enabled. Safe to call
|
|
28
|
+
* unauthenticated.
|
|
29
|
+
*/
|
|
30
|
+
async fetchAuthConfig() {
|
|
31
|
+
const res = await fetch(`${this.baseUrl}/v1/auth/config`, {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
credentials: 'include',
|
|
34
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
return null;
|
|
38
|
+
const body = (await res.json());
|
|
39
|
+
if (!body || typeof body !== 'object')
|
|
40
|
+
return null;
|
|
41
|
+
const si = body.signIn ?? {};
|
|
42
|
+
const su = body.signUp ?? {};
|
|
43
|
+
return {
|
|
44
|
+
signIn: {
|
|
45
|
+
emailPassword: !!si.emailPassword,
|
|
46
|
+
usernamePassword: !!si.usernamePassword,
|
|
47
|
+
magicLink: !!si.magicLink,
|
|
48
|
+
emailCode: !!si.emailCode,
|
|
49
|
+
phoneOtp: !!si.phoneOtp,
|
|
50
|
+
passkey: !!si.passkey,
|
|
51
|
+
},
|
|
52
|
+
signUp: {
|
|
53
|
+
enabled: su.enabled !== false,
|
|
54
|
+
email: !!su.email,
|
|
55
|
+
emailRequired: !!su.emailRequired,
|
|
56
|
+
username: su.username
|
|
57
|
+
? {
|
|
58
|
+
required: su.username.required === true,
|
|
59
|
+
minLength: Number(su.username.minLength ?? 4),
|
|
60
|
+
maxLength: Number(su.username.maxLength ?? 64),
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
password: !!su.password,
|
|
64
|
+
phone: !!su.phone,
|
|
65
|
+
firstAndLastName: su.firstAndLastName ? { required: su.firstAndLastName.required === true } : null,
|
|
66
|
+
verifyAtSignUp: !!su.verifyAtSignUp,
|
|
67
|
+
},
|
|
68
|
+
socialProviders: Array.isArray(body.socialProviders) ? body.socialProviders.map(String) : [],
|
|
69
|
+
web3Wallets: Array.isArray(body.web3Wallets) ? body.web3Wallets.map(String) : [],
|
|
70
|
+
legal: {
|
|
71
|
+
termsOfServiceUrl: typeof body.legal?.termsOfServiceUrl === 'string' ? body.legal.termsOfServiceUrl : null,
|
|
72
|
+
privacyPolicyUrl: typeof body.legal?.privacyPolicyUrl === 'string' ? body.legal.privacyPolicyUrl : null,
|
|
73
|
+
requireConsent: body.legal?.requireConsent === true,
|
|
74
|
+
},
|
|
75
|
+
branding: {
|
|
76
|
+
primaryColor: typeof body.branding?.primaryColor === 'string' ? body.branding.primaryColor : null,
|
|
77
|
+
logoUrl: typeof body.branding?.logoUrl === 'string' ? body.branding.logoUrl : null,
|
|
78
|
+
appName: typeof body.branding?.appName === 'string' ? body.branding.appName : null,
|
|
79
|
+
removeBranding: body.branding?.removeBranding === true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
getSessionFromCookies() {
|
|
84
|
+
const jwt = getSessionJwtFromDocumentCookie();
|
|
85
|
+
if (!jwt)
|
|
86
|
+
return null;
|
|
87
|
+
const claims = decodeJwtPayload(jwt);
|
|
88
|
+
return { jwt, claims };
|
|
89
|
+
}
|
|
90
|
+
async refresh() {
|
|
91
|
+
const res = await fetch(`${this.baseUrl}/v1/auth/sessions/refresh`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
credentials: 'include',
|
|
94
|
+
headers: this.headers({ 'content-type': 'application/json' }),
|
|
95
|
+
body: JSON.stringify({}),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok)
|
|
98
|
+
return { ok: false };
|
|
99
|
+
const next = this.getSessionFromCookies();
|
|
100
|
+
if (!next)
|
|
101
|
+
return { ok: false };
|
|
102
|
+
return { ok: true, jwt: next.jwt, claims: next.claims };
|
|
103
|
+
}
|
|
104
|
+
async fetchCurrentUser() {
|
|
105
|
+
const res = await fetch(`${this.baseUrl}/v1/auth/sessions/current`, {
|
|
106
|
+
method: 'GET',
|
|
107
|
+
credentials: 'include',
|
|
108
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok)
|
|
111
|
+
return null;
|
|
112
|
+
const body = (await res.json());
|
|
113
|
+
if (!body?.user?.id)
|
|
114
|
+
return null;
|
|
115
|
+
const u = body.user;
|
|
116
|
+
return {
|
|
117
|
+
id: String(u.id),
|
|
118
|
+
email: u.email ?? null,
|
|
119
|
+
username: u.username ?? null,
|
|
120
|
+
firstName: u.firstName ?? null,
|
|
121
|
+
lastName: u.lastName ?? null,
|
|
122
|
+
publicMetadata: (u.publicMetadata ?? {}),
|
|
123
|
+
createdAt: String(u.createdAt ?? new Date().toISOString()),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async fetchOrganization(orgId) {
|
|
127
|
+
const res = await fetch(`${this.baseUrl}/v1/orgs/${encodeURIComponent(orgId)}`, {
|
|
128
|
+
method: 'GET',
|
|
129
|
+
credentials: 'include',
|
|
130
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok)
|
|
133
|
+
return null;
|
|
134
|
+
const body = (await res.json());
|
|
135
|
+
if (!body?.organization?.id)
|
|
136
|
+
return null;
|
|
137
|
+
const o = body.organization;
|
|
138
|
+
return { id: String(o.id), name: String(o.name), slug: String(o.slug) };
|
|
139
|
+
}
|
|
140
|
+
async fetchPlans() {
|
|
141
|
+
const res = await fetch(`${this.baseUrl}/v1/billing/plans`, {
|
|
142
|
+
method: 'GET',
|
|
143
|
+
credentials: 'include',
|
|
144
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok)
|
|
147
|
+
return [];
|
|
148
|
+
const body = (await res.json());
|
|
149
|
+
if (!Array.isArray(body?.plans))
|
|
150
|
+
return [];
|
|
151
|
+
return body.plans.map((p) => ({
|
|
152
|
+
id: String(p.id),
|
|
153
|
+
name: String(p.name),
|
|
154
|
+
description: p.description ?? null,
|
|
155
|
+
monthlyPriceCents: Number(p.monthlyPriceCents ?? 0),
|
|
156
|
+
annualPriceCents: p.annualPriceCents == null ? null : Number(p.annualPriceCents),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns the caller's effective plan + attached features, used by
|
|
161
|
+
* `useAuth().has({ plan })` / `has({ feature })` and `<Protect>` /
|
|
162
|
+
* `<Show when>`. Always served by the instance API; returns `null` for
|
|
163
|
+
* signed-out users.
|
|
164
|
+
*/
|
|
165
|
+
async fetchMyBilling() {
|
|
166
|
+
const res = await fetch(`${this.baseUrl}/v1/billing/me`, {
|
|
167
|
+
method: 'GET',
|
|
168
|
+
credentials: 'include',
|
|
169
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
170
|
+
});
|
|
171
|
+
if (!res.ok)
|
|
172
|
+
return null;
|
|
173
|
+
const body = (await res.json());
|
|
174
|
+
if (!body)
|
|
175
|
+
return null;
|
|
176
|
+
return {
|
|
177
|
+
plan: body.plan
|
|
178
|
+
? {
|
|
179
|
+
id: String(body.plan.id),
|
|
180
|
+
key: body.plan.key ?? null,
|
|
181
|
+
name: String(body.plan.name ?? ''),
|
|
182
|
+
}
|
|
183
|
+
: null,
|
|
184
|
+
features: Array.isArray(body.features)
|
|
185
|
+
? body.features.map((f) => ({
|
|
186
|
+
key: String(f.key),
|
|
187
|
+
name: String(f.name ?? f.key),
|
|
188
|
+
description: f.description ?? undefined,
|
|
189
|
+
}))
|
|
190
|
+
: [],
|
|
191
|
+
status: body.status ?? null,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async fetchSubscription() {
|
|
195
|
+
const res = await fetch(`${this.baseUrl}/v1/billing/subscription`, {
|
|
196
|
+
method: 'GET',
|
|
197
|
+
credentials: 'include',
|
|
198
|
+
headers: this.headers({ accept: 'application/json' }),
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok)
|
|
201
|
+
return null;
|
|
202
|
+
const body = (await res.json());
|
|
203
|
+
if (!body?.subscription?.id)
|
|
204
|
+
return null;
|
|
205
|
+
const s = body.subscription;
|
|
206
|
+
return {
|
|
207
|
+
id: String(s.id),
|
|
208
|
+
status: String(s.status),
|
|
209
|
+
planId: String(s.planId),
|
|
210
|
+
currentPeriodEnd: s.currentPeriodEnd ?? null,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async createCheckoutSession(planId, billingCycle = 'monthly') {
|
|
214
|
+
const res = await fetch(`${this.baseUrl}/v1/billing/checkout`, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
credentials: 'include',
|
|
217
|
+
headers: this.headers({ 'content-type': 'application/json', accept: 'application/json' }),
|
|
218
|
+
body: JSON.stringify({ planId, billingCycle }),
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok)
|
|
221
|
+
throw new Error(`checkout_failed_${res.status}`);
|
|
222
|
+
const body = (await res.json());
|
|
223
|
+
if (!body?.url)
|
|
224
|
+
throw new Error('checkout_no_url');
|
|
225
|
+
return { url: body.url };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Renders its children only when there's an active session. Server-side
|
|
4
|
+
* it renders nothing until the provider hydrates, so wrap it in a client
|
|
5
|
+
* component tree (the AuthfyioProvider already is).
|
|
6
|
+
*/
|
|
7
|
+
export declare function SignedIn({ children }: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}): React.ReactElement | null;
|
|
10
|
+
/**
|
|
11
|
+
* Renders its children only when the session is absent or expired.
|
|
12
|
+
*/
|
|
13
|
+
export declare function SignedOut({ children }: {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}): React.ReactElement | null;
|
|
16
|
+
export type ProtectProps = {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
/** Require an org role (matches the session's `org_role` claim). */
|
|
19
|
+
role?: string;
|
|
20
|
+
/** Require an active subscription to a plan by stable key (e.g. "pro"). */
|
|
21
|
+
plan?: string;
|
|
22
|
+
/** Require a specific feature attached to the user's active plan. */
|
|
23
|
+
feature?: string;
|
|
24
|
+
/** Fallback rendered when the user is signed out or fails the check. */
|
|
25
|
+
fallback?: React.ReactNode;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Renders `children` when the user is signed in AND satisfies every provided
|
|
29
|
+
* check (`role`, `plan`, `feature`). Otherwise renders `fallback`. Use this
|
|
30
|
+
* for component-level authorization inside larger unprotected pages.
|
|
31
|
+
*/
|
|
32
|
+
export declare function Protect({ children, role, plan, feature, fallback, }: ProtectProps): React.ReactElement | null;
|
|
33
|
+
export type ShowProps = {
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
/** Conditions; all must match (AND). */
|
|
36
|
+
when: {
|
|
37
|
+
plan?: string;
|
|
38
|
+
feature?: string;
|
|
39
|
+
role?: string;
|
|
40
|
+
};
|
|
41
|
+
/** Rendered when the check fails. */
|
|
42
|
+
fallback?: React.ReactNode;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* customisable `<Show when={{plan,feature,role}}>`. Thin wrapper over
|
|
46
|
+
* `auth.has({...})`. Returns nothing while the check is loading so you
|
|
47
|
+
* don't flash the fallback state.
|
|
48
|
+
*/
|
|
49
|
+
export declare function Show({ children, when, fallback }: ShowProps): React.ReactElement | null;
|
|
50
|
+
export type RedirectToSignInProps = {
|
|
51
|
+
/** Target path on your app that renders the sign-in form (default: '/sign-in'). */
|
|
52
|
+
signInUrl?: string;
|
|
53
|
+
/** Optional query parameter carrying the caller URL so you can return after sign-in. */
|
|
54
|
+
afterSignInParam?: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Client-side redirect to the sign-in page. Fire-and-forget: it navigates on
|
|
58
|
+
* mount via `window.location.replace` so it also works inside React trees
|
|
59
|
+
* without `next/navigation`. Prefer `<SignedOut>` + this for guarded UIs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function RedirectToSignIn({ signInUrl, afterSignInParam, }: RedirectToSignInProps): null;
|
|
62
|
+
export type RedirectToSignUpProps = {
|
|
63
|
+
signUpUrl?: string;
|
|
64
|
+
afterSignUpParam?: string;
|
|
65
|
+
};
|
|
66
|
+
export declare function RedirectToSignUp({ signUpUrl, afterSignUpParam, }: RedirectToSignUpProps): null;
|
|
67
|
+
export type SignOutButtonProps = {
|
|
68
|
+
children?: React.ReactNode;
|
|
69
|
+
/** Redirect target after sign-out completes. Defaults to '/'. */
|
|
70
|
+
redirectUrl?: string;
|
|
71
|
+
className?: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Convenience button — calls `POST /v1/auth/sign-out` on the instance API,
|
|
75
|
+
* then navigates to `redirectUrl`. Use inside `<SignedIn>` or check `isSignedIn`
|
|
76
|
+
* yourself. Uses the provider's configured baseUrl.
|
|
77
|
+
*/
|
|
78
|
+
export declare function SignOutButton({ children, redirectUrl, className }: SignOutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAI/F;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAIhG;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,QAAe,GAChB,EAAE,YAAY,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAY1C;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,wCAAwC;IACxC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,qCAAqC;IACrC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAe,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAK9F;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,SAAsB,EACtB,gBAAiC,GAClC,EAAE,qBAAqB,GAAG,IAAI,CAS9B;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,SAAsB,EACtB,gBAAiC,GAClC,EAAE,qBAAqB,GAAG,IAAI,CAS9B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,WAAiB,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAiB3F"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { useSession, useAuthfyio } from './provider.js';
|
|
5
|
+
import { useAuth } from './hooks.js';
|
|
6
|
+
/**
|
|
7
|
+
* Renders its children only when there's an active session. Server-side
|
|
8
|
+
* it renders nothing until the provider hydrates, so wrap it in a client
|
|
9
|
+
* component tree (the AuthfyioProvider already is).
|
|
10
|
+
*/
|
|
11
|
+
export function SignedIn({ children }) {
|
|
12
|
+
const s = useSession();
|
|
13
|
+
if (s.status !== 'signed_in')
|
|
14
|
+
return null;
|
|
15
|
+
return _jsx(_Fragment, { children: children });
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Renders its children only when the session is absent or expired.
|
|
19
|
+
*/
|
|
20
|
+
export function SignedOut({ children }) {
|
|
21
|
+
const s = useSession();
|
|
22
|
+
if (s.status === 'signed_in')
|
|
23
|
+
return null;
|
|
24
|
+
return _jsx(_Fragment, { children: children });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Renders `children` when the user is signed in AND satisfies every provided
|
|
28
|
+
* check (`role`, `plan`, `feature`). Otherwise renders `fallback`. Use this
|
|
29
|
+
* for component-level authorization inside larger unprotected pages.
|
|
30
|
+
*/
|
|
31
|
+
export function Protect({ children, role, plan, feature, fallback = null, }) {
|
|
32
|
+
const auth = useAuth();
|
|
33
|
+
if (!auth.isSignedIn)
|
|
34
|
+
return _jsx(_Fragment, { children: fallback });
|
|
35
|
+
const checks = [];
|
|
36
|
+
if (role)
|
|
37
|
+
checks.push(auth.has({ role }));
|
|
38
|
+
if (plan)
|
|
39
|
+
checks.push(auth.has({ plan }));
|
|
40
|
+
if (feature)
|
|
41
|
+
checks.push(auth.has({ feature }));
|
|
42
|
+
// While any async check is still resolving (`undefined`), render nothing to
|
|
43
|
+
// avoid a flash of the fallback. Once all checks settle to booleans, render.
|
|
44
|
+
if (checks.some((c) => c === undefined))
|
|
45
|
+
return null;
|
|
46
|
+
if (checks.some((c) => c === false))
|
|
47
|
+
return _jsx(_Fragment, { children: fallback });
|
|
48
|
+
return _jsx(_Fragment, { children: children });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* customisable `<Show when={{plan,feature,role}}>`. Thin wrapper over
|
|
52
|
+
* `auth.has({...})`. Returns nothing while the check is loading so you
|
|
53
|
+
* don't flash the fallback state.
|
|
54
|
+
*/
|
|
55
|
+
export function Show({ children, when, fallback = null }) {
|
|
56
|
+
const auth = useAuth();
|
|
57
|
+
const result = auth.has(when);
|
|
58
|
+
if (result === undefined)
|
|
59
|
+
return null;
|
|
60
|
+
return result ? _jsx(_Fragment, { children: children }) : _jsx(_Fragment, { children: fallback });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Client-side redirect to the sign-in page. Fire-and-forget: it navigates on
|
|
64
|
+
* mount via `window.location.replace` so it also works inside React trees
|
|
65
|
+
* without `next/navigation`. Prefer `<SignedOut>` + this for guarded UIs.
|
|
66
|
+
*/
|
|
67
|
+
export function RedirectToSignIn({ signInUrl = '/sign-in', afterSignInParam = 'redirect_url', }) {
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (typeof window === 'undefined')
|
|
70
|
+
return;
|
|
71
|
+
const next = window.location.pathname + window.location.search;
|
|
72
|
+
const url = new URL(signInUrl, window.location.origin);
|
|
73
|
+
if (next && next !== '/')
|
|
74
|
+
url.searchParams.set(afterSignInParam, next);
|
|
75
|
+
window.location.replace(url.toString());
|
|
76
|
+
}, [signInUrl, afterSignInParam]);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
export function RedirectToSignUp({ signUpUrl = '/sign-up', afterSignUpParam = 'redirect_url', }) {
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (typeof window === 'undefined')
|
|
82
|
+
return;
|
|
83
|
+
const next = window.location.pathname + window.location.search;
|
|
84
|
+
const url = new URL(signUpUrl, window.location.origin);
|
|
85
|
+
if (next && next !== '/')
|
|
86
|
+
url.searchParams.set(afterSignUpParam, next);
|
|
87
|
+
window.location.replace(url.toString());
|
|
88
|
+
}, [signUpUrl, afterSignUpParam]);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convenience button — calls `POST /v1/auth/sign-out` on the instance API,
|
|
93
|
+
* then navigates to `redirectUrl`. Use inside `<SignedIn>` or check `isSignedIn`
|
|
94
|
+
* yourself. Uses the provider's configured baseUrl.
|
|
95
|
+
*/
|
|
96
|
+
export function SignOutButton({ children, redirectUrl = '/', className }) {
|
|
97
|
+
const { client } = useAuthfyio();
|
|
98
|
+
async function onClick() {
|
|
99
|
+
try {
|
|
100
|
+
await fetch(`${client.baseUrl ?? ''}/v1/auth/sign-out`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
credentials: 'include',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if (typeof window !== 'undefined')
|
|
107
|
+
window.location.href = redirectUrl;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return (_jsx("button", { type: "button", onClick: onClick, className: className, children: children ?? 'Sign out' }));
|
|
111
|
+
}
|