@edcalderon/auth 1.1.2 โ†’ 1.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] - 2026-03-02
4
+
5
+ ### Added
6
+
7
+ - ๐Ÿ”— Added agnostic Web3 support to the core `SignInOptions` (`options.provider === 'web3'`).
8
+ - ๐Ÿ”— Upgraded `SupabaseClient` and `HybridClient` adapters to natively call Supabase's `signInWithWeb3` standard.
9
+ - ๐Ÿ“ Documented Wagmi and `@solana/wallet-adapter` implementation examples in README.
10
+
11
+ ## [1.1.3] - 2026-03-02
12
+
13
+ ### Docs
14
+
15
+ - ๐Ÿ“ Fully rewrote README to document the new `v1.1.0` Universal Compatibility (Web + Next.js + React Native/Expo) APIs and export paths.
16
+
3
17
  ## [1.1.2] - 2026-03-02
4
18
 
5
19
  ### Changed
package/README.md CHANGED
@@ -4,500 +4,283 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@edcalderon/auth?style=flat-square&color=10b981)](https://www.npmjs.com/package/@edcalderon/auth)
5
5
  [![GitHub](https://img.shields.io/badge/GitHub-Repository-181717?style=flat-square&logo=github)](https://github.com/edcalderon/my-second-brain/tree/main/packages/auth)
6
6
 
7
- A universal, **provider-agnostic** authentication orchestration package for React applications. Swap between Supabase, Firebase, Directus, Google OAuth, or any custom provider without changing a single line of component code.
7
+ A universal, **provider-agnostic** authentication orchestration package designed for absolute runtime portability. One abstraction that works flawlessly across React Web `(18.x/19.x)`, Next.js `(14/15)`, and React Native/Expo `(SDK 50+)`.
8
+
9
+ Swap between Supabase, Firebase, Hybrid, or any custom provider without changing a single line of your UX component code.
8
10
 
9
11
  ---
10
12
 
11
- ## ๐Ÿ“‹ Latest Changes (v1.1.2)
13
+ ## ๐Ÿ“‹ Latest Changes (v1.2.0)
12
14
 
13
- ### Changed
15
+ ### Added
14
16
 
15
- - ๐Ÿงช Test release to verify readme-maintainer guard via pre-push hook
17
+ - ๐Ÿ”— Added agnostic Web3 support to the core `SignInOptions` (`options.provider === 'web3'`).
18
+ - ๐Ÿ”— Upgraded `SupabaseClient` and `HybridClient` adapters to natively call Supabase's `signInWithWeb3` standard.
19
+ - ๐Ÿ“ Documented Wagmi and `@solana/wallet-adapter` implementation examples in README.
16
20
 
17
21
  For full version history, see [CHANGELOG.md](./CHANGELOG.md) and [GitHub releases](https://github.com/edcalderon/my-second-brain/releases)
18
22
 
19
23
  ---
20
24
 
21
- ## ๐Ÿ—๏ธ Architecture
22
-
23
- The package follows a **Single Source of Truth** model with a **Federated OAuth Strategy**:
24
-
25
- - **Principal Database (Source of Truth)**: Supabase anchors user identities, metadata, roles, and RLS policies in PostgreSQL (`auth.users`, `auth.identities`).
26
- - **OAuth / Identity Providers**: External services (Firebase, Directus, native Google OAuth, Auth0, etc.) handle frontend login bridges or federated SSO flows.
27
- - **The Orchestrator (`@edcalderon/auth`)**: A thin bridge layer that exposes generic interfaces (`User`, `AuthClient`). Applications consume a unified context without coupling to any specific vendor.
25
+ ## ๐Ÿš€ Runtime Support Matrix
28
26
 
29
- **Architecture Flow:**
27
+ | Target Runtime | Engine / Framework | Notes | Supported Flow Semantics |
28
+ |----------------|--------------------|-------|-----------------|
29
+ | **Web** | React, Vite, SPA | Standard web APIs available (`window`) | `popup`, `redirect` |
30
+ | **Server** | Next.js Client | Compatible with App Router Contexts | `redirect`, `popup` |
31
+ | **Native** | Expo/React Native | Clean native bundles, strictly no web assumptions | `native` |
30
32
 
31
- 1. **Frontend Applications** `=>` consume **`@edcalderon/auth`** via `useAuth()`
32
- 2. **`@edcalderon/auth`** orchestrates the adapters:
33
- - `=>` **Supabase Adapter** (Direct Session)
34
- - `=>` **Hybrid Bridge** (Firebase OAuth + Supabase Session)
35
- - `=>` **Custom Adapters** (e.g. Directus SSO, Auth0)
36
- 3. **Identity Providers** (Firebase/Directus) `=>` Sync Session to **Supabase**
37
- 4. **Supabase** `=>` Manages Roles & Scopes in the **PostgreSQL** Database
38
33
  ---
39
34
 
40
- ## Features
35
+ ## ๐Ÿ—๏ธ Architecture
41
36
 
42
- - ๐ŸŽฏ **Provider-Agnostic** โ€” one interface, any backend
43
- - โš›๏ธ **React-First** โ€” `AuthProvider` + `useAuth` hook with full TypeScript types
44
- - ๐Ÿ”Œ **Extensible Adapter System** โ€” implement `AuthClient` to add any provider
45
- - ๐Ÿ”€ **Hybrid Flows** โ€” Firebase popup โ†’ Supabase session bridging out of the box
46
- - ๐Ÿ›ก๏ธ **Unified User Model** โ€” `User` type normalizes identities across providers
47
- - ๐Ÿ”‘ **Session Token Access** โ€” `getSessionToken()` for API calls regardless of provider
48
- - ๐Ÿ“ฆ **Tree-Shakeable** โ€” import only the adapters you need
49
- - ๐Ÿ—๏ธ **Zero Lock-In** โ€” swap providers by changing one line of dependency injection
37
+ The package follows a **Single Source of Truth** model with a **Federated OAuth Strategy**:
38
+ - **Principal Database (Source of Truth)**: Supabase anchors user identities, metadata, roles, and RLS policies in PostgreSQL.
39
+ - **The Orchestrator (`@edcalderon/auth`)**: A thin bridge layer exposing a generic interface (`User`, `AuthClient`).
40
+
41
+ The UI consumes a **unified context** disconnected entirely from provider implementations.
50
42
 
51
43
  ---
52
44
 
53
45
  ## Installation
54
46
 
55
- ### From NPM (public)
56
-
57
47
  ```bash
58
48
  npm install @edcalderon/auth
59
49
  # or
60
50
  pnpm add @edcalderon/auth
61
- # or
62
- yarn add @edcalderon/auth
63
- ```
64
-
65
- ### From Monorepo (internal workspace)
66
-
67
- ```bash
68
- pnpm --filter <your-app> add @edcalderon/auth@workspace:*
69
51
  ```
70
52
 
71
53
  ### Peer Dependencies
72
54
 
73
- Install the peer dependencies for your chosen provider(s):
55
+ Install peers depending on what adapters you use. (The NPM module avoids forcing packages you won't ship to Native vs Web via strict subpath exports).
74
56
 
75
57
  ```bash
76
- # For Supabase
58
+ # Core requirements
59
+ pnpm add react react-dom
60
+
61
+ # Supabase (Adapter peers)
77
62
  pnpm add @supabase/supabase-js
78
63
 
79
- # For Firebase
64
+ # Firebase (Hybrid/Pure peers)
80
65
  pnpm add firebase
81
66
 
82
- # For Hybrid (Firebase + Supabase)
83
- pnpm add @supabase/supabase-js firebase
67
+ # Expo/Native Only
68
+ pnpm add react-native
84
69
  ```
85
70
 
86
- > **Note:** `react` and `react-dom` (v18+ or v19+) are required peer dependencies.
87
-
88
71
  ---
89
72
 
90
- ## Quick Start
73
+ ## Subpath Exports (Crucial for RN/Next.js compatibility)
91
74
 
92
- ### 1. Choose Your Provider
75
+ The package avoids bleeding `window` or `document` objects into Expo bundles or bleeding heavy native dependencies into web implementations via strict environment exports:
93
76
 
94
- | Provider | Class | Peer Dependency | Use Case |
95
- |----------|-------|-----------------|----------|
96
- | Supabase | `SupabaseClient` | `@supabase/supabase-js` | Direct Supabase Auth |
97
- | Firebase | `FirebaseClient` | `firebase` | Firebase-only applications |
98
- | Hybrid | `HybridClient` | Both | Firebase popup โ†’ Supabase session |
99
- | Custom | Implement `AuthClient` | Your choice | Directus, Auth0, Keycloak, etc. |
100
-
101
- ### 2. Create the Provider Wrapper
102
-
103
- #### Supabase (Direct)
104
-
105
- ```tsx
106
- // components/auth/AuthProvider.tsx
107
- "use client";
77
+ - `@edcalderon/auth` (Shared Core interfaces + Contexts)
78
+ - `@edcalderon/auth/supabase`
79
+ - `@edcalderon/auth/firebase-web`
80
+ - `@edcalderon/auth/firebase-native`
81
+ - `@edcalderon/auth/hybrid-web`
82
+ - `@edcalderon/auth/hybrid-native`
108
83
 
109
- import { AuthProvider as UniversalAuthProvider, SupabaseClient, useAuth as useUniversalAuth } from "@edcalderon/auth";
110
- import { supabase } from "@/lib/supabase";
111
- import { useMemo, type ReactNode } from "react";
84
+ ---
112
85
 
113
- export function AuthProvider({ children }: { children: ReactNode }) {
114
- const client = useMemo(() => new SupabaseClient(supabase), []);
115
- return <UniversalAuthProvider client={client}>{children}</UniversalAuthProvider>;
116
- }
86
+ ## Quick Start (Web & Next.js)
117
87
 
118
- export const useAuth = useUniversalAuth;
119
- ```
88
+ ### 1. Unified React Component UI (Usage)
120
89
 
121
- #### Hybrid (Firebase โ†’ Supabase)
90
+ Your component code is 100% blind to what provider or environment you are using. The `signIn` orchestration handles translating standard intent into provider actions seamlessly.
122
91
 
123
92
  ```tsx
124
93
  "use client";
125
-
126
- import { AuthProvider as UniversalAuthProvider, HybridClient, useAuth as useUniversalAuth } from "@edcalderon/auth";
127
- import { supabase } from "@/lib/supabase";
128
- import { auth, googleProvider, signInWithPopup, signOut, GoogleAuthProvider } from "@/lib/firebase";
129
- import { useMemo, type ReactNode } from "react";
130
-
131
- export function AuthProvider({ children }: { children: ReactNode }) {
132
- const client = useMemo(() => new HybridClient({
133
- supabase,
134
- firebaseAuth: auth,
135
- firebaseMethods: {
136
- signInWithPopup,
137
- signOut,
138
- credentialFromResult: GoogleAuthProvider.credentialFromResult,
139
- },
140
- googleProvider,
141
- }), []);
142
-
143
- return <UniversalAuthProvider client={client}>{children}</UniversalAuthProvider>;
144
- }
145
-
146
- export const useAuth = useUniversalAuth;
147
- ```
148
-
149
- ### 3. Use in Components
150
-
151
- Every component consumes **identical signatures** regardless of which provider is active:
152
-
153
- ```tsx
154
- import { useAuth } from "@/components/auth/AuthProvider";
94
+ import { useAuth } from "@edcalderon/auth";
155
95
 
156
96
  export default function Dashboard() {
157
- const { user, loading, error, signInWithGoogle, signOutUser } = useAuth();
97
+ const { user, loading, error, signIn, signOutUser } = useAuth();
158
98
 
159
99
  if (loading) return <Spinner />;
160
100
  if (error) return <p>Error: {error}</p>;
161
- if (!user) return <button onClick={() => signInWithGoogle()}>Sign In with Google</button>;
101
+
102
+ if (!user) {
103
+ return (
104
+ <button onClick={() => signIn({ provider: "google", flow: "popup" })}>
105
+ Sign In with Google
106
+ </button>
107
+ );
108
+ }
162
109
 
163
110
  return (
164
111
  <div>
165
- <p>Welcome, {user.email} (via {user.provider})</p>
112
+ <p>Welcome, {user.email}</p>
166
113
  <button onClick={signOutUser}>Sign Out</button>
167
114
  </div>
168
115
  );
169
116
  }
170
117
  ```
171
118
 
172
- ---
173
-
174
- ## ๐Ÿ”Œ Extensibility โ€” Custom Adapters
119
+ ### 2. Web3 Crypto Wallets (Wagmi / Solana)
175
120
 
176
- The core strength of `@edcalderon/auth` is that **any authentication provider** can be integrated by implementing the `AuthClient` interface. No changes to your React components are required.
121
+ Because the orchestration is provider-blind, you can easily pair it with libraries like `wagmi` or `@solana/wallet-adapter-react`.
177
122
 
178
- ### The `AuthClient` Interface
123
+ ```tsx
124
+ "use client";
125
+ import { useAuth } from "@edcalderon/auth";
126
+ import { useWallet } from "@solana/wallet-adapter-react";
127
+
128
+ export function SolanaLogin() {
129
+ const { signIn } = useAuth();
130
+ const wallet = useWallet();
131
+
132
+ const handleWeb3SignIn = () => {
133
+ if (!wallet.connected) return;
134
+
135
+ signIn({
136
+ provider: "web3",
137
+ web3: {
138
+ chain: "solana",
139
+ wallet: wallet.wallet?.adapter // Pass the raw wallet adapter
140
+ }
141
+ });
142
+ }
179
143
 
180
- ```typescript
181
- export interface AuthClient {
182
- getUser(): Promise<User | null>;
183
- signInWithEmail(email: string, password: string): Promise<User>;
184
- signInWithGoogle(redirectTo?: string): Promise<void>;
185
- signOut(): Promise<void>;
186
- onAuthStateChange(callback: (user: User | null) => void): () => void;
187
- getSessionToken(): Promise<string | null>;
144
+ return <button onClick={handleWeb3SignIn}>Sign In with Solana</button>;
188
145
  }
189
146
  ```
190
147
 
191
- ### The `User` Type
192
-
193
- ```typescript
194
- export interface User {
195
- id: string;
196
- email?: string;
197
- avatarUrl?: string;
198
- provider?: string;
199
- metadata?: Record<string, any>;
200
- }
201
- ```
148
+ ### 3. Provider Top-Level App Injectors
202
149
 
203
- ### Example: Directus Adapter
150
+ Wire the environment appropriate class up at your app root.
204
151
 
205
- A custom Directus adapter that uses Directus SSO (e.g., Google OAuth through Directus) and optionally syncs sessions back to Supabase:
152
+ #### Supabase (Web/Native Universal)
206
153
 
207
- ```typescript
208
- import type { AuthClient, User } from "@edcalderon/auth";
154
+ ```tsx
155
+ "use client";
156
+ import { AuthProvider } from "@edcalderon/auth";
157
+ import { SupabaseClient } from "@edcalderon/auth/supabase";
158
+ import { supabase } from "@/lib/supabase";
209
159
 
210
- interface DirectusClientOptions {
211
- directusUrl: string;
212
- supabase?: any; // Optional: sync to Supabase as source of truth
160
+ export function AppProviders({ children }) {
161
+ // Works perfectly in both web and Next.js out of the box
162
+ const client = new SupabaseClient({ supabase });
163
+ return <AuthProvider client={client}>{children}</AuthProvider>;
213
164
  }
165
+ ```
214
166
 
215
- export class DirectusClient implements AuthClient {
216
- private directusUrl: string;
217
- private supabase: any;
218
- private currentUser: User | null = null;
219
- private listeners: Set<(user: User | null) => void> = new Set();
220
-
221
- constructor(options: DirectusClientOptions) {
222
- this.directusUrl = options.directusUrl;
223
- this.supabase = options.supabase;
224
- }
225
-
226
- private mapUser(directusUser: any): User | null {
227
- if (!directusUser) return null;
228
- return {
229
- id: directusUser.id,
230
- email: directusUser.email,
231
- avatarUrl: directusUser.avatar
232
- ? `${this.directusUrl}/assets/${directusUser.avatar}`
233
- : undefined,
234
- provider: "directus",
235
- metadata: {
236
- firstName: directusUser.first_name,
237
- lastName: directusUser.last_name,
238
- role: directusUser.role,
239
- },
240
- };
241
- }
242
-
243
- async getUser(): Promise<User | null> {
244
- try {
245
- const res = await fetch(`${this.directusUrl}/users/me`, {
246
- credentials: "include",
247
- });
248
- if (!res.ok) return null;
249
- const { data } = await res.json();
250
- this.currentUser = this.mapUser(data);
251
- return this.currentUser;
252
- } catch {
253
- return null;
254
- }
255
- }
256
-
257
- async signInWithEmail(email: string, password: string): Promise<User> {
258
- const res = await fetch(`${this.directusUrl}/auth/login`, {
259
- method: "POST",
260
- headers: { "Content-Type": "application/json" },
261
- credentials: "include",
262
- body: JSON.stringify({ email, password }),
263
- });
264
- if (!res.ok) throw new Error("Directus login failed");
265
- const user = await this.getUser();
266
- if (!user) throw new Error("No user after login");
267
- this.notifyListeners(user);
268
-
269
- // Optional: sync to Supabase
270
- if (this.supabase) {
271
- await this.syncToSupabase(user);
272
- }
273
- return user;
274
- }
275
-
276
- async signInWithGoogle(redirectTo?: string): Promise<void> {
277
- // Directus SSO โ€” redirect to Directus Google OAuth endpoint
278
- const callback = redirectTo || window.location.origin + "/auth/callback";
279
- window.location.href =
280
- `${this.directusUrl}/auth/login/google?redirect=${encodeURIComponent(callback)}`;
281
- }
282
-
283
- async signOut(): Promise<void> {
284
- await fetch(`${this.directusUrl}/auth/logout`, {
285
- method: "POST",
286
- credentials: "include",
287
- });
288
- this.currentUser = null;
289
- this.notifyListeners(null);
290
- }
167
+ #### Hybrid (Firebase UI โ†’ Supabase Database Session Bridging for Web)
291
168
 
292
- onAuthStateChange(callback: (user: User | null) => void): () => void {
293
- this.listeners.add(callback);
294
- return () => { this.listeners.delete(callback); };
295
- }
169
+ Perfect if you want Firebase to handle the Google popup, but want to automatically consume the ID Token into Supabase to maintain your DB as the source of truth!
296
170
 
297
- async getSessionToken(): Promise<string | null> {
298
- try {
299
- const res = await fetch(`${this.directusUrl}/auth/refresh`, {
300
- method: "POST",
301
- credentials: "include",
302
- });
303
- if (!res.ok) return null;
304
- const { data } = await res.json();
305
- return data?.access_token ?? null;
306
- } catch {
307
- return null;
308
- }
309
- }
171
+ ```tsx
172
+ "use client";
173
+ import { AuthProvider } from "@edcalderon/auth";
174
+ import { HybridWebClient } from "@edcalderon/auth/hybrid-web";
175
+ import { supabase } from "@/lib/supabase";
176
+ import { auth, signInWithPopup, signOut, GoogleAuthProvider } from "@/lib/firebase";
310
177
 
311
- private notifyListeners(user: User | null) {
312
- this.listeners.forEach((cb) => cb(user));
313
- }
178
+ export function AppProviders({ children }) {
179
+ const client = new HybridWebClient({
180
+ supabase,
181
+ firebaseAuth: auth,
182
+ firebaseMethods: { signInWithPopup, signOut, credentialFromResult: GoogleAuthProvider.credentialFromResult },
183
+ googleProvider: new GoogleAuthProvider(),
184
+ });
314
185
 
315
- private async syncToSupabase(user: User) {
316
- // Sync user identity to Supabase as source of truth
317
- // Implementation depends on your Supabase setup
318
- }
186
+ return <AuthProvider client={client}>{children}</AuthProvider>;
319
187
  }
320
188
  ```
321
189
 
322
- **Usage:**
323
-
324
- ```tsx
325
- import { AuthProvider as UniversalAuthProvider } from "@edcalderon/auth";
326
- import { DirectusClient } from "./adapters/DirectusClient";
190
+ ---
327
191
 
328
- const client = new DirectusClient({
329
- directusUrl: "https://directus.example.com",
330
- supabase: supabaseInstance, // optional sync
331
- });
192
+ ## Quick Start (Expo & React Native)
332
193
 
333
- <UniversalAuthProvider client={client}>
334
- <App />
335
- </UniversalAuthProvider>
336
- ```
194
+ React Native apps cannot safely utilize Web's window or popup assumptions. Because of the unified typings, your components never have to change, you just wire up the specific native adapters.
337
195
 
338
- ### Example: Auth0 Adapter (Skeleton)
196
+ ### Hybrid Strategy Native (`expo-auth-session`)
339
197
 
340
- ```typescript
341
- import type { AuthClient, User } from "@edcalderon/auth";
198
+ Instead of trying to pop up Firebase Web via polyfills, explicitly hand over native execution capabilities down to the adapter utilizing React Native Expo equivalents.
342
199
 
343
- export class Auth0Client implements AuthClient {
344
- constructor(private auth0: any) {}
200
+ ```tsx
201
+ import { AuthProvider } from "@edcalderon/auth";
202
+ import { HybridNativeClient } from "@edcalderon/auth/hybrid-native";
203
+ import { supabase } from "@/lib/supabase";
204
+ import { auth, signInWithCredential } from "firebase/auth";
205
+ import * as Google from 'expo-auth-session/providers/google'; // Or react-native-google-signin
206
+
207
+ export function ExpoProviders({ children }) {
208
+ // 1. You provide strictly native capability functions out of your Expo ecosystem
209
+ const nativeGoogleHandler = async (options) => {
210
+ // e.g promptAsync()
211
+ // Exchange credential response for Firebase Native Credentials
212
+ // Return { credential, idToken }
213
+ };
214
+
215
+ const client = new HybridNativeClient({
216
+ supabase,
217
+ firebaseAuth: auth,
218
+ firebaseMethods: { signInWithCredential, signOut },
219
+ oauthHandlers: {
220
+ "google": nativeGoogleHandler
221
+ }
222
+ });
345
223
 
346
- async getUser(): Promise<User | null> { /* ... */ }
347
- async signInWithEmail(email: string, password: string): Promise<User> { /* ... */ }
348
- async signInWithGoogle(redirectTo?: string): Promise<void> { /* ... */ }
349
- async signOut(): Promise<void> { /* ... */ }
350
- onAuthStateChange(callback: (user: User | null) => void): () => void { /* ... */ }
351
- async getSessionToken(): Promise<string | null> { /* ... */ }
224
+ return <AuthProvider client={client}>{children}</AuthProvider>;
352
225
  }
353
226
  ```
354
227
 
355
- By implementing the `AuthClient` interface, any provider fits into the same `<AuthProvider>` and `useAuth()` workflow โ€” **zero changes** to your component tree.
228
+ Now, clicking `signIn({ provider: "google", flow: "native" })` from anywhere inside your Expo app safely triggers `nativeGoogleHandler` and orchestrates Firebase translation down to Supabase seamlessly behind the scenes!
356
229
 
357
230
  ---
358
231
 
359
- ## Built-in Adapters
360
-
361
- ### `SupabaseClient`
362
-
363
- Direct Supabase Auth adapter. Uses `@supabase/supabase-js` for session management, OAuth, and email/password.
364
-
365
- ```typescript
366
- import { SupabaseClient } from "@edcalderon/auth";
367
- import { createClient } from "@supabase/supabase-js";
368
-
369
- const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
370
- const client = new SupabaseClient(supabase);
371
- ```
372
-
373
- **Features:**
374
- - Email/password sign-in (`signInWithPassword`)
375
- - Google OAuth (`signInWithOAuth`)
376
- - Session token via `getSession().access_token`
377
- - Real-time auth state changes via `onAuthStateChange`
232
+ ## ๐Ÿ”Œ API Reference - Extensibility
378
233
 
379
- ### `FirebaseClient`
380
-
381
- Firebase-only adapter. Uses Firebase Auth methods via dependency injection (tree-shaking friendly).
382
-
383
- ```typescript
384
- import { FirebaseClient } from "@edcalderon/auth";
385
- import { getAuth, GoogleAuthProvider, signInWithEmailAndPassword, signInWithPopup, signOut, onAuthStateChanged } from "firebase/auth";
386
-
387
- const auth = getAuth(app);
388
- const client = new FirebaseClient(auth, {
389
- signInWithEmailAndPassword,
390
- signInWithPopup,
391
- signOut,
392
- onAuthStateChanged,
393
- }, new GoogleAuthProvider());
394
- ```
395
-
396
- **Features:**
397
- - Email/password sign-in
398
- - Google popup sign-in
399
- - Firebase ID token via `getIdToken()`
400
- - Real-time auth state changes
401
-
402
- ### `HybridClient`
234
+ ### The `AuthClient` Interface
403
235
 
404
- Bridges Firebase Google popup โ†’ Supabase `signInWithIdToken`. Perfect for apps that need Firebase's popup UX but Supabase as the data backend.
236
+ The core strength of `@edcalderon/auth` is that **any authentication service** can be mapped directly onto the `AuthClient` type, exposing typed portability out-of-the-box.
405
237
 
406
238
  ```typescript
407
- import { HybridClient } from "@edcalderon/auth";
408
-
409
- const client = new HybridClient({
410
- supabase,
411
- firebaseAuth: auth,
412
- firebaseMethods: { signInWithPopup, signOut, credentialFromResult: GoogleAuthProvider.credentialFromResult },
413
- googleProvider: new GoogleAuthProvider(),
414
- });
415
- ```
416
-
417
- **Features:**
418
- - Firebase popup โ†’ extracts Google OIDC ID token โ†’ passes to Supabase `signInWithIdToken`
419
- - Graceful fallback to Supabase native OAuth when Firebase is not configured
420
- - Dual sign-out (Firebase + Supabase)
421
- - Auth state tracked via Supabase session
422
-
423
- ---
424
-
425
- ## API Reference
426
-
427
- ### `<AuthProvider>`
239
+ type AuthRuntime = "web" | "native" | "server";
240
+ type OAuthFlow = "popup" | "redirect" | "native";
241
+
242
+ export interface Web3SignInOptions {
243
+ chain: "ethereum" | "solana" | "bitcoin";
244
+ wallet?: any;
245
+ message?: string;
246
+ signature?: string;
247
+ }
428
248
 
429
- React context provider that wraps your app with authentication state.
249
+ export interface SignInOptions {
250
+ provider?: "google" | "apple" | "github" | "web3" | string;
251
+ flow?: OAuthFlow;
252
+ redirectUri?: string;
253
+ web3?: Web3SignInOptions;
254
+ }
430
255
 
431
- ```tsx
432
- <AuthProvider client={authClient}>
433
- {children}
434
- </AuthProvider>
256
+ export interface AuthClient {
257
+ runtime: AuthRuntime;
258
+ capabilities(): { runtime: AuthRuntime; supportedFlows: OAuthFlow[] };
259
+
260
+ getUser(): Promise<User | null>;
261
+ signInWithEmail(email: string, password: string): Promise<User>;
262
+ signIn(options: SignInOptions): Promise<void>;
263
+ signOut(): Promise<void>;
264
+
265
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
266
+ getSessionToken(): Promise<string | null>;
267
+ }
435
268
  ```
436
269
 
437
- | Prop | Type | Description |
438
- |------|------|-------------|
439
- | `client` | `AuthClient` | The authentication adapter instance |
440
- | `children` | `ReactNode` | Child components |
441
-
442
- ### `useAuth()`
443
-
444
- React hook that returns the current authentication state and actions.
270
+ ### The `User` Type
445
271
 
446
272
  ```typescript
447
- const {
448
- user, // User | null
449
- loading, // boolean
450
- error, // string | null
451
- client, // AuthClient (direct access)
452
- signInWithEmail, // (email: string, password: string) => Promise<User>
453
- signInWithGoogle, // (redirectTo?: string) => Promise<void>
454
- signOutUser, // () => Promise<void>
455
- } = useAuth();
273
+ export interface User {
274
+ id: string;
275
+ email?: string;
276
+ avatarUrl?: string;
277
+ provider?: string;
278
+ providerUserId?: string;
279
+ roles?: string[];
280
+ metadata?: Record<string, any>;
281
+ }
456
282
  ```
457
283
 
458
- > **Note:** `useAuth()` must be called within an `<AuthProvider>`. It will throw if used outside the provider tree.
459
-
460
- ---
461
-
462
- ## Publishing & Releases
463
-
464
- ### Automated NPM Publishing
465
-
466
- This package uses GitHub Actions for automated publishing to NPM when version tags are created.
467
-
468
- #### Release Process
469
-
470
- 1. **Update Version**: Bump the version in `package.json`
471
- ```bash
472
- cd packages/auth
473
- npm version patch # or minor, major
474
- ```
475
-
476
- 2. **Create Git Tag**: Create and push an `auth-v*` tag
477
- ```bash
478
- git add packages/auth/package.json
479
- git commit -m "chore(auth): bump version to X.Y.Z"
480
- git tag auth-vX.Y.Z
481
- git push && git push --tags
482
- ```
483
-
484
- 3. **Automated Publishing**: GitHub Actions will automatically build and publish to NPM
485
-
486
- #### NPM Token Setup
487
-
488
- To enable automated publishing:
489
-
490
- 1. Go to [NPM](https://www.npmjs.com/) โ†’ Access Tokens โ†’ Generate New Token
491
- 2. Create a token with **Automation** scope
492
- 3. Add to GitHub repository secrets as `NPM_TOKEN`
493
-
494
- ---
495
-
496
- ## Documentation
497
-
498
- - **[CHANGELOG](CHANGELOG.md)** โ€” Version history and changes
499
- - **[GitHub Releases](https://github.com/edcalderon/my-second-brain/releases)** โ€” Tagged releases
500
-
501
284
  ---
502
285
 
503
286
  ## License
@@ -48,6 +48,20 @@ export class HybridNativeClient {
48
48
  }
49
49
  async signIn(options) {
50
50
  const provider = options.provider || "google";
51
+ if (provider === "web3") {
52
+ if (!options.web3)
53
+ throw new Error("CONFIG_ERROR: options.web3 is required when provider is 'web3'");
54
+ const { error } = await this.supabase.auth.signInWithWeb3({
55
+ // @ts-ignore - Supabase TS types might be strict (e.g. 0x${string}), bypass for agnostic adapter
56
+ chain: options.web3.chain,
57
+ message: options.web3.message,
58
+ signature: options.web3.signature,
59
+ wallet: options.web3.wallet,
60
+ });
61
+ if (error)
62
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
63
+ return;
64
+ }
51
65
  // Pure Supabase route
52
66
  if (!this.firebaseAuth || !this.methods || !this.oauthHandlers[provider]) {
53
67
  console.warn(`Native OAuth via Hybrid fallback for '${provider}' targeting Supabase purely`);
@@ -48,6 +48,20 @@ export class HybridWebClient {
48
48
  }
49
49
  async signIn(options) {
50
50
  const provider = options.provider || "google";
51
+ if (provider === "web3") {
52
+ if (!options.web3)
53
+ throw new Error("CONFIG_ERROR: options.web3 is required when provider is 'web3'");
54
+ const { error } = await this.supabase.auth.signInWithWeb3({
55
+ // @ts-ignore - Supabase TS types might be strict (e.g. 0x${string}), bypass for agnostic adapter
56
+ chain: options.web3.chain,
57
+ message: options.web3.message,
58
+ signature: options.web3.signature,
59
+ wallet: options.web3.wallet,
60
+ });
61
+ if (error)
62
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
63
+ return;
64
+ }
51
65
  if (!this.firebaseAuth || !this.methods || !this.googleProvider) {
52
66
  console.warn("Firebase not configured on Hybrid fallback, using Supabase pure auth");
53
67
  const { error } = await this.supabase.auth.signInWithOAuth({
@@ -50,7 +50,22 @@ export class SupabaseClient {
50
50
  }
51
51
  async signIn(options) {
52
52
  if (!options.provider) {
53
- throw new Error("CONFIG_ERROR: options.provider is required for Supabase OAuth");
53
+ throw new Error("CONFIG_ERROR: options.provider is required for Supabase OAuth or Web3");
54
+ }
55
+ if (options.provider === "web3") {
56
+ if (!options.web3) {
57
+ throw new Error("CONFIG_ERROR: options.web3 is required when provider is 'web3'");
58
+ }
59
+ const { error } = await this.supabase.auth.signInWithWeb3({
60
+ // @ts-ignore - Supabase TS types might be strict (e.g. 0x${string}), bypass for agnostic adapter
61
+ chain: options.web3.chain,
62
+ message: options.web3.message,
63
+ signature: options.web3.signature,
64
+ wallet: options.web3.wallet,
65
+ });
66
+ if (error)
67
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
68
+ return;
54
69
  }
55
70
  let redirectTo = options.redirectUri;
56
71
  // Apply web assumption ONLY if strictly web
package/dist/types.d.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  export type AuthRuntime = "web" | "native" | "server";
2
2
  export type OAuthFlow = "popup" | "redirect" | "native";
3
+ export interface Web3SignInOptions {
4
+ chain: "ethereum" | "solana" | "bitcoin";
5
+ wallet?: any;
6
+ message?: string;
7
+ signature?: string;
8
+ }
3
9
  export interface SignInOptions {
4
- provider?: "google" | "apple" | "github" | string;
10
+ provider?: "google" | "apple" | "github" | "web3" | string;
5
11
  flow?: OAuthFlow;
6
12
  redirectUri?: string;
13
+ web3?: Web3SignInOptions;
7
14
  }
8
15
  export type AuthErrorCode = "CONFIG_ERROR" | "UNSUPPORTED_FLOW" | "NETWORK_ERROR" | "PROVIDER_ERROR" | "SESSION_ERROR";
9
16
  export interface User {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edcalderon/auth",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "A universal, provider-agnostic authentication package (Web + Next.js + Expo/React Native)",
5
5
  "exports": {
6
6
  ".": {