@reauth-dev/sdk 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.
@@ -0,0 +1,448 @@
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/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ AuthProvider: () => AuthProvider,
24
+ ProtectedRoute: () => ProtectedRoute,
25
+ useAuth: () => useAuth,
26
+ useAuthContext: () => useAuthContext
27
+ });
28
+ module.exports = __toCommonJS(react_exports);
29
+
30
+ // src/react/useAuth.ts
31
+ var import_react = require("react");
32
+
33
+ // src/client.ts
34
+ function createReauthClient(config) {
35
+ const { domain } = config;
36
+ const baseUrl = `https://reauth.${domain}/api/public`;
37
+ return {
38
+ /**
39
+ * Redirect the user to the reauth.dev login page.
40
+ * After successful login, they'll be redirected back to your configured redirect URL.
41
+ */
42
+ login() {
43
+ if (typeof window === "undefined") {
44
+ throw new Error("login() can only be called in browser");
45
+ }
46
+ window.location.href = `https://reauth.${domain}/`;
47
+ },
48
+ /**
49
+ * Check if the user is authenticated.
50
+ * Returns session info including user ID, email, and roles.
51
+ */
52
+ async getSession() {
53
+ const res = await fetch(`${baseUrl}/auth/session`, {
54
+ credentials: "include"
55
+ });
56
+ return res.json();
57
+ },
58
+ /**
59
+ * Refresh the access token using the refresh token.
60
+ * Call this when getSession() returns valid: false but no error_code.
61
+ * @returns true if refresh succeeded, false otherwise
62
+ */
63
+ async refresh() {
64
+ const res = await fetch(`${baseUrl}/auth/refresh`, {
65
+ method: "POST",
66
+ credentials: "include"
67
+ });
68
+ return res.ok;
69
+ },
70
+ /**
71
+ * Get an access token for Bearer authentication.
72
+ * Use this when calling your own API that uses local token verification.
73
+ *
74
+ * @returns TokenResponse with access token, or null if not authenticated
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const tokenResponse = await reauth.getToken();
79
+ * if (tokenResponse) {
80
+ * fetch('/api/data', {
81
+ * headers: { Authorization: `Bearer ${tokenResponse.accessToken}` }
82
+ * });
83
+ * }
84
+ * ```
85
+ */
86
+ async getToken() {
87
+ try {
88
+ const res = await fetch(`${baseUrl}/auth/token`, {
89
+ method: "GET",
90
+ credentials: "include"
91
+ });
92
+ if (!res.ok) {
93
+ if (res.status === 401) return null;
94
+ throw new Error(`Failed to get token: ${res.status}`);
95
+ }
96
+ const data = await res.json();
97
+ return {
98
+ accessToken: data.access_token,
99
+ expiresIn: data.expires_in,
100
+ tokenType: data.token_type
101
+ };
102
+ } catch {
103
+ return null;
104
+ }
105
+ },
106
+ /**
107
+ * Log out the user by clearing all session cookies.
108
+ */
109
+ async logout() {
110
+ await fetch(`${baseUrl}/auth/logout`, {
111
+ method: "POST",
112
+ credentials: "include"
113
+ });
114
+ },
115
+ /**
116
+ * Delete the user's own account (self-service).
117
+ * @returns true if deletion succeeded, false otherwise
118
+ */
119
+ async deleteAccount() {
120
+ const res = await fetch(`${baseUrl}/auth/account`, {
121
+ method: "DELETE",
122
+ credentials: "include"
123
+ });
124
+ return res.ok;
125
+ },
126
+ // ========================================================================
127
+ // Billing Methods
128
+ // ========================================================================
129
+ /**
130
+ * Get available subscription plans for the domain.
131
+ * Only returns public plans sorted by display order.
132
+ */
133
+ async getPlans() {
134
+ const res = await fetch(`${baseUrl}/billing/plans`, {
135
+ credentials: "include"
136
+ });
137
+ if (!res.ok) return [];
138
+ const data = await res.json();
139
+ return data.map(
140
+ (p) => ({
141
+ id: p.id,
142
+ code: p.code,
143
+ name: p.name,
144
+ description: p.description,
145
+ priceCents: p.price_cents,
146
+ currency: p.currency,
147
+ interval: p.interval,
148
+ intervalCount: p.interval_count,
149
+ trialDays: p.trial_days,
150
+ features: p.features,
151
+ displayOrder: p.display_order
152
+ })
153
+ );
154
+ },
155
+ /**
156
+ * Get the current user's subscription status.
157
+ */
158
+ async getSubscription() {
159
+ const res = await fetch(`${baseUrl}/billing/subscription`, {
160
+ credentials: "include"
161
+ });
162
+ const data = await res.json();
163
+ return {
164
+ id: data.id,
165
+ planCode: data.plan_code,
166
+ planName: data.plan_name,
167
+ status: data.status,
168
+ currentPeriodEnd: data.current_period_end,
169
+ trialEnd: data.trial_end,
170
+ cancelAtPeriodEnd: data.cancel_at_period_end
171
+ };
172
+ },
173
+ /**
174
+ * Create a Stripe checkout session to subscribe to a plan.
175
+ * @param planCode The plan code to subscribe to
176
+ * @param successUrl URL to redirect to after successful payment
177
+ * @param cancelUrl URL to redirect to if checkout is canceled
178
+ * @returns Checkout session with URL to redirect the user to
179
+ */
180
+ async createCheckout(planCode, successUrl, cancelUrl) {
181
+ const res = await fetch(`${baseUrl}/billing/checkout`, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ credentials: "include",
185
+ body: JSON.stringify({
186
+ plan_code: planCode,
187
+ success_url: successUrl,
188
+ cancel_url: cancelUrl
189
+ })
190
+ });
191
+ if (!res.ok) {
192
+ const err = await res.json().catch(() => ({}));
193
+ throw new Error(err.message || "Failed to create checkout session");
194
+ }
195
+ const data = await res.json();
196
+ return { checkoutUrl: data.checkout_url };
197
+ },
198
+ /**
199
+ * Redirect user to subscribe to a plan.
200
+ * Creates a checkout session and redirects to Stripe.
201
+ * @param planCode The plan code to subscribe to
202
+ */
203
+ async subscribe(planCode) {
204
+ if (typeof window === "undefined") {
205
+ throw new Error("subscribe() can only be called in browser");
206
+ }
207
+ const currentUrl = window.location.href;
208
+ const { checkoutUrl } = await this.createCheckout(
209
+ planCode,
210
+ currentUrl,
211
+ currentUrl
212
+ );
213
+ window.location.href = checkoutUrl;
214
+ },
215
+ /**
216
+ * Open the Stripe customer portal for managing subscription.
217
+ * @param returnUrl URL to return to after leaving the portal
218
+ */
219
+ async openBillingPortal(returnUrl) {
220
+ if (typeof window === "undefined") {
221
+ throw new Error("openBillingPortal() can only be called in browser");
222
+ }
223
+ const res = await fetch(`${baseUrl}/billing/portal`, {
224
+ method: "POST",
225
+ headers: { "Content-Type": "application/json" },
226
+ credentials: "include",
227
+ body: JSON.stringify({
228
+ return_url: returnUrl || window.location.href
229
+ })
230
+ });
231
+ if (!res.ok) {
232
+ throw new Error("Failed to open billing portal");
233
+ }
234
+ const data = await res.json();
235
+ window.location.href = data.portal_url;
236
+ },
237
+ /**
238
+ * Cancel the user's subscription at period end.
239
+ * @returns true if cancellation succeeded
240
+ */
241
+ async cancelSubscription() {
242
+ const res = await fetch(`${baseUrl}/billing/cancel`, {
243
+ method: "POST",
244
+ credentials: "include"
245
+ });
246
+ return res.ok;
247
+ },
248
+ // ========================================================================
249
+ // Balance Methods
250
+ // ========================================================================
251
+ /**
252
+ * Get the current user's balance.
253
+ * @returns Object with the current balance
254
+ */
255
+ async getBalance() {
256
+ const res = await fetch(`${baseUrl}/balance`, {
257
+ credentials: "include"
258
+ });
259
+ if (!res.ok) {
260
+ if (res.status === 401) throw new Error("Not authenticated");
261
+ throw new Error(`Failed to get balance: ${res.status}`);
262
+ }
263
+ return res.json();
264
+ },
265
+ /**
266
+ * Get the current user's balance transaction history.
267
+ * @param opts - Optional pagination (limit, offset)
268
+ * @returns Object with array of transactions (newest first)
269
+ */
270
+ async getTransactions(opts) {
271
+ const params = new URLSearchParams();
272
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
273
+ if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
274
+ const qs = params.toString();
275
+ const res = await fetch(
276
+ `${baseUrl}/balance/transactions${qs ? `?${qs}` : ""}`,
277
+ { credentials: "include" }
278
+ );
279
+ if (!res.ok) {
280
+ if (res.status === 401) throw new Error("Not authenticated");
281
+ throw new Error(`Failed to get transactions: ${res.status}`);
282
+ }
283
+ const data = await res.json();
284
+ return {
285
+ transactions: data.transactions.map(
286
+ (t) => ({
287
+ id: t.id,
288
+ amountDelta: t.amount_delta,
289
+ reason: t.reason,
290
+ balanceAfter: t.balance_after,
291
+ createdAt: t.created_at
292
+ })
293
+ )
294
+ };
295
+ }
296
+ };
297
+ }
298
+
299
+ // src/react/useAuth.ts
300
+ function useAuth(config) {
301
+ const client = (0, import_react.useMemo)(() => createReauthClient(config), [config.domain]);
302
+ const [state, setState] = (0, import_react.useState)({
303
+ user: null,
304
+ loading: true,
305
+ error: null,
306
+ isOnWaitlist: false,
307
+ waitlistPosition: null
308
+ });
309
+ const checkSession = (0, import_react.useCallback)(async () => {
310
+ try {
311
+ let session = await client.getSession();
312
+ if (!session.valid && !session.error_code && !session.end_user_id) {
313
+ const refreshed = await client.refresh();
314
+ if (refreshed) {
315
+ session = await client.getSession();
316
+ }
317
+ }
318
+ if (session.error_code === "ACCOUNT_SUSPENDED") {
319
+ setState({
320
+ user: null,
321
+ loading: false,
322
+ error: "Account suspended",
323
+ isOnWaitlist: false,
324
+ waitlistPosition: null
325
+ });
326
+ return;
327
+ }
328
+ if (session.valid && session.waitlist_position) {
329
+ setState({
330
+ user: {
331
+ id: session.end_user_id,
332
+ email: session.email,
333
+ roles: session.roles || []
334
+ },
335
+ loading: false,
336
+ error: null,
337
+ isOnWaitlist: true,
338
+ waitlistPosition: session.waitlist_position
339
+ });
340
+ return;
341
+ }
342
+ if (session.valid && session.end_user_id) {
343
+ setState({
344
+ user: {
345
+ id: session.end_user_id,
346
+ email: session.email,
347
+ roles: session.roles || []
348
+ },
349
+ loading: false,
350
+ error: null,
351
+ isOnWaitlist: false,
352
+ waitlistPosition: null
353
+ });
354
+ return;
355
+ }
356
+ setState({
357
+ user: null,
358
+ loading: false,
359
+ error: null,
360
+ isOnWaitlist: false,
361
+ waitlistPosition: null
362
+ });
363
+ } catch {
364
+ setState({
365
+ user: null,
366
+ loading: false,
367
+ error: "Auth check failed",
368
+ isOnWaitlist: false,
369
+ waitlistPosition: null
370
+ });
371
+ }
372
+ }, [client]);
373
+ (0, import_react.useEffect)(() => {
374
+ checkSession();
375
+ }, [checkSession]);
376
+ const logout = (0, import_react.useCallback)(async () => {
377
+ await client.logout();
378
+ setState({
379
+ user: null,
380
+ loading: false,
381
+ error: null,
382
+ isOnWaitlist: false,
383
+ waitlistPosition: null
384
+ });
385
+ }, [client]);
386
+ return {
387
+ ...state,
388
+ login: client.login,
389
+ logout,
390
+ refetch: checkSession
391
+ };
392
+ }
393
+
394
+ // src/react/AuthProvider.tsx
395
+ var import_react2 = require("react");
396
+ var import_jsx_runtime = require("react/jsx-runtime");
397
+ var AuthContext = (0, import_react2.createContext)(null);
398
+ function AuthProvider({ config, children }) {
399
+ const auth = useAuth(config);
400
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value: auth, children });
401
+ }
402
+ function useAuthContext() {
403
+ const context = (0, import_react2.useContext)(AuthContext);
404
+ if (!context) {
405
+ throw new Error("useAuthContext must be used within AuthProvider");
406
+ }
407
+ return context;
408
+ }
409
+
410
+ // src/react/ProtectedRoute.tsx
411
+ var import_react3 = require("react");
412
+ var import_jsx_runtime2 = require("react/jsx-runtime");
413
+ function ProtectedRoute({
414
+ children,
415
+ fallback = null,
416
+ onUnauthenticated,
417
+ onWaitlist
418
+ }) {
419
+ const { user, loading, isOnWaitlist, login } = useAuthContext();
420
+ (0, import_react3.useEffect)(() => {
421
+ if (!loading && !user) {
422
+ if (onUnauthenticated) {
423
+ onUnauthenticated();
424
+ } else {
425
+ login();
426
+ }
427
+ }
428
+ }, [loading, user, login, onUnauthenticated]);
429
+ (0, import_react3.useEffect)(() => {
430
+ if (!loading && isOnWaitlist && onWaitlist) {
431
+ onWaitlist();
432
+ }
433
+ }, [loading, isOnWaitlist, onWaitlist]);
434
+ if (loading) {
435
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
436
+ }
437
+ if (!user || isOnWaitlist) {
438
+ return null;
439
+ }
440
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
441
+ }
442
+ // Annotate the CommonJS export names for ESM import in node:
443
+ 0 && (module.exports = {
444
+ AuthProvider,
445
+ ProtectedRoute,
446
+ useAuth,
447
+ useAuthContext
448
+ });
@@ -0,0 +1,154 @@
1
+ import {
2
+ createReauthClient
3
+ } from "../chunk-JX2J36FS.mjs";
4
+
5
+ // src/react/useAuth.ts
6
+ import { useState, useEffect, useCallback, useMemo } from "react";
7
+ function useAuth(config) {
8
+ const client = useMemo(() => createReauthClient(config), [config.domain]);
9
+ const [state, setState] = useState({
10
+ user: null,
11
+ loading: true,
12
+ error: null,
13
+ isOnWaitlist: false,
14
+ waitlistPosition: null
15
+ });
16
+ const checkSession = useCallback(async () => {
17
+ try {
18
+ let session = await client.getSession();
19
+ if (!session.valid && !session.error_code && !session.end_user_id) {
20
+ const refreshed = await client.refresh();
21
+ if (refreshed) {
22
+ session = await client.getSession();
23
+ }
24
+ }
25
+ if (session.error_code === "ACCOUNT_SUSPENDED") {
26
+ setState({
27
+ user: null,
28
+ loading: false,
29
+ error: "Account suspended",
30
+ isOnWaitlist: false,
31
+ waitlistPosition: null
32
+ });
33
+ return;
34
+ }
35
+ if (session.valid && session.waitlist_position) {
36
+ setState({
37
+ user: {
38
+ id: session.end_user_id,
39
+ email: session.email,
40
+ roles: session.roles || []
41
+ },
42
+ loading: false,
43
+ error: null,
44
+ isOnWaitlist: true,
45
+ waitlistPosition: session.waitlist_position
46
+ });
47
+ return;
48
+ }
49
+ if (session.valid && session.end_user_id) {
50
+ setState({
51
+ user: {
52
+ id: session.end_user_id,
53
+ email: session.email,
54
+ roles: session.roles || []
55
+ },
56
+ loading: false,
57
+ error: null,
58
+ isOnWaitlist: false,
59
+ waitlistPosition: null
60
+ });
61
+ return;
62
+ }
63
+ setState({
64
+ user: null,
65
+ loading: false,
66
+ error: null,
67
+ isOnWaitlist: false,
68
+ waitlistPosition: null
69
+ });
70
+ } catch {
71
+ setState({
72
+ user: null,
73
+ loading: false,
74
+ error: "Auth check failed",
75
+ isOnWaitlist: false,
76
+ waitlistPosition: null
77
+ });
78
+ }
79
+ }, [client]);
80
+ useEffect(() => {
81
+ checkSession();
82
+ }, [checkSession]);
83
+ const logout = useCallback(async () => {
84
+ await client.logout();
85
+ setState({
86
+ user: null,
87
+ loading: false,
88
+ error: null,
89
+ isOnWaitlist: false,
90
+ waitlistPosition: null
91
+ });
92
+ }, [client]);
93
+ return {
94
+ ...state,
95
+ login: client.login,
96
+ logout,
97
+ refetch: checkSession
98
+ };
99
+ }
100
+
101
+ // src/react/AuthProvider.tsx
102
+ import { createContext, useContext } from "react";
103
+ import { jsx } from "react/jsx-runtime";
104
+ var AuthContext = createContext(null);
105
+ function AuthProvider({ config, children }) {
106
+ const auth = useAuth(config);
107
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: auth, children });
108
+ }
109
+ function useAuthContext() {
110
+ const context = useContext(AuthContext);
111
+ if (!context) {
112
+ throw new Error("useAuthContext must be used within AuthProvider");
113
+ }
114
+ return context;
115
+ }
116
+
117
+ // src/react/ProtectedRoute.tsx
118
+ import { useEffect as useEffect2 } from "react";
119
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
120
+ function ProtectedRoute({
121
+ children,
122
+ fallback = null,
123
+ onUnauthenticated,
124
+ onWaitlist
125
+ }) {
126
+ const { user, loading, isOnWaitlist, login } = useAuthContext();
127
+ useEffect2(() => {
128
+ if (!loading && !user) {
129
+ if (onUnauthenticated) {
130
+ onUnauthenticated();
131
+ } else {
132
+ login();
133
+ }
134
+ }
135
+ }, [loading, user, login, onUnauthenticated]);
136
+ useEffect2(() => {
137
+ if (!loading && isOnWaitlist && onWaitlist) {
138
+ onWaitlist();
139
+ }
140
+ }, [loading, isOnWaitlist, onWaitlist]);
141
+ if (loading) {
142
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
143
+ }
144
+ if (!user || isOnWaitlist) {
145
+ return null;
146
+ }
147
+ return /* @__PURE__ */ jsx2(Fragment, { children });
148
+ }
149
+ export {
150
+ AuthProvider,
151
+ ProtectedRoute,
152
+ useAuth,
153
+ useAuthContext
154
+ };