@edcalderon/auth 1.0.2 β†’ 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.3] - 2026-03-01
4
+
5
+ ### Fixed
6
+
7
+ - πŸ› Updated import from `@ed/auth` (old internal alias) to `@edcalderon/auth` in dashboard consumer
8
+ - πŸ“ Added `update-readme` script β€” uses `versioning update-readme` to keep README in sync with CHANGELOG
9
+ - πŸ”„ Versioning package used as dev dependency for README maintenance
10
+
3
11
  ## [1.0.2] - 2026-03-01
4
12
 
5
13
  ### Fixed
package/README.md CHANGED
@@ -8,15 +8,13 @@ A universal, **provider-agnostic** authentication orchestration package for Reac
8
8
 
9
9
  ---
10
10
 
11
- ## πŸ“‹ Latest Changes (v1.0.0)
11
+ ## πŸ“‹ Latest Changes (v1.0.3)
12
12
 
13
- ### Initial Release
13
+ ### Fixed
14
14
 
15
- - ✨ Provider-agnostic `AuthClient` interface
16
- - πŸ”Œ Built-in adapters: **Supabase**, **Firebase**, **Hybrid** (Firebaseβ†’Supabase)
17
- - βš›οΈ React `AuthProvider` context and `useAuth` hook
18
- - πŸ›‘οΈ Unified `User` type across all providers
19
- - πŸ”‘ Session token access via `getSessionToken()`
15
+ - πŸ› Updated import from `@ed/auth` (old internal alias) to `@edcalderon/auth` in dashboard consumer
16
+ - πŸ“ Added `update-readme` script β€” uses `versioning update-readme` to keep README in sync with CHANGELOG
17
+ - πŸ”„ Versioning package used as dev dependency for README maintenance
20
18
 
21
19
  For full version history, see [CHANGELOG.md](./CHANGELOG.md) and [GitHub releases](https://github.com/edcalderon/my-second-brain/releases)
22
20
 
@@ -30,20 +28,15 @@ The package follows a **Single Source of Truth** model with a **Federated OAuth
30
28
  - **OAuth / Identity Providers**: External services (Firebase, Directus, native Google OAuth, Auth0, etc.) handle frontend login bridges or federated SSO flows.
31
29
  - **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.
32
30
 
33
- ```mermaid
34
- graph TD
35
- UI[Frontend Applications] -->|useAuth| EdAuth["@edcalderon/auth"]
36
- EdAuth -->|Direct Session| Supabase(Supabase)
37
- EdAuth -->|Federated Bridge| Firebase(Firebase OAuth)
38
- EdAuth -->|Custom Adapter| Directus(Directus SSO)
39
- EdAuth -->|Custom Adapter| Custom(Auth0 / Custom)
40
-
41
- Firebase -->|Sync Session| Supabase
42
- Directus -->|Sync Session| Supabase
43
-
44
- Supabase -->|Roles & Scopes| DB[(PostgreSQL)]
45
- ```
31
+ **Architecture Flow:**
46
32
 
33
+ 1. **Frontend Applications** `=>` consume **`@edcalderon/auth`** via `useAuth()`
34
+ 2. **`@edcalderon/auth`** orchestrates the adapters:
35
+ - `=>` **Supabase Adapter** (Direct Session)
36
+ - `=>` **Hybrid Bridge** (Firebase OAuth + Supabase Session)
37
+ - `=>` **Custom Adapters** (e.g. Directus SSO, Auth0)
38
+ 3. **Identity Providers** (Firebase/Directus) `=>` Sync Session to **Supabase**
39
+ 4. **Supabase** `=>` Manages Roles & Scopes in the **PostgreSQL** Database
47
40
  ---
48
41
 
49
42
  ## Features
@@ -6,7 +6,9 @@ interface AuthContextValue {
6
6
  error: string | null;
7
7
  client: AuthClient;
8
8
  signInWithEmail: (email: string, password: string) => Promise<User>;
9
+ /** @deprecated Use signIn instead */
9
10
  signInWithGoogle: (redirectTo?: string) => Promise<void>;
11
+ signIn: (options: import('./types').SignInOptions) => Promise<void>;
10
12
  signOutUser: () => Promise<void>;
11
13
  }
12
14
  export declare function AuthProvider({ client, children }: {
@@ -60,6 +60,16 @@ export function AuthProvider({ client, children }) {
60
60
  throw err;
61
61
  }
62
62
  },
63
+ signIn: async (options) => {
64
+ setError(null);
65
+ try {
66
+ await client.signIn(options);
67
+ }
68
+ catch (err) {
69
+ setError(err.message);
70
+ throw err;
71
+ }
72
+ },
63
73
  signOutUser: async () => {
64
74
  setError(null);
65
75
  try {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./types";
2
- export * from "./providers/SupabaseClient";
3
- export * from "./providers/FirebaseClient";
4
- export * from "./providers/HybridClient";
5
2
  export * from "./AuthProvider";
3
+ export * from "./providers/SupabaseClient";
4
+ export * from "./providers/FirebaseWebClient";
5
+ export { FirebaseWebClient as FirebaseClient } from "./providers/FirebaseWebClient";
6
+ export * from "./providers/HybridWebClient";
7
+ export { HybridWebClient as HybridClient } from "./providers/HybridWebClient";
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export * from "./types";
2
- export * from "./providers/SupabaseClient";
3
- export * from "./providers/FirebaseClient";
4
- export * from "./providers/HybridClient";
5
2
  export * from "./AuthProvider";
3
+ // Core exports the types and provider. Provider implementations are loaded from subpaths.
4
+ // Maintain backwards compatibility exports for v1.1.x
5
+ export * from "./providers/SupabaseClient";
6
+ export * from "./providers/FirebaseWebClient";
7
+ export { FirebaseWebClient as FirebaseClient } from "./providers/FirebaseWebClient";
8
+ export * from "./providers/HybridWebClient";
9
+ export { HybridWebClient as HybridClient } from "./providers/HybridWebClient";
6
10
  //# sourceMappingURL=index.js.map
@@ -1,22 +1,2 @@
1
- import type { Auth } from "firebase/auth";
2
- import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
3
- import { AuthClient, User } from "../types";
4
- export interface FirebaseMethods {
5
- signInWithEmailAndPassword: any;
6
- signInWithPopup: any;
7
- signOut: any;
8
- onAuthStateChanged: any;
9
- }
10
- export declare class FirebaseClient implements AuthClient {
11
- private auth;
12
- private methods;
13
- private googleProvider;
14
- constructor(auth: Auth, methods: FirebaseMethods, googleProvider: GoogleAuthProviderType);
15
- private mapUser;
16
- getUser(): Promise<User | null>;
17
- signInWithEmail(email: string, password: string): Promise<User>;
18
- signInWithGoogle(redirectTo?: string): Promise<void>;
19
- signOut(): Promise<void>;
20
- onAuthStateChange(callback: (user: User | null) => void): () => void;
21
- getSessionToken(): Promise<string | null>;
22
- }
1
+ export * from "./FirebaseWebClient";
2
+ export { FirebaseWebClient as FirebaseClient } from "./FirebaseWebClient";
@@ -1,57 +1,4 @@
1
- export class FirebaseClient {
2
- auth;
3
- methods;
4
- googleProvider;
5
- constructor(auth, methods, googleProvider) {
6
- this.auth = auth;
7
- this.methods = methods;
8
- this.googleProvider = googleProvider;
9
- }
10
- mapUser(user) {
11
- if (!user)
12
- return null;
13
- return {
14
- id: user.uid,
15
- email: user.email || undefined,
16
- avatarUrl: user.photoURL || undefined,
17
- metadata: { displayName: user.displayName },
18
- };
19
- }
20
- async getUser() {
21
- if (this.auth.currentUser)
22
- return this.mapUser(this.auth.currentUser);
23
- return new Promise((resolve) => {
24
- const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
25
- unsubscribe();
26
- resolve(this.mapUser(user));
27
- });
28
- });
29
- }
30
- async signInWithEmail(email, password) {
31
- const userCredential = await this.methods.signInWithEmailAndPassword(this.auth, email, password);
32
- return this.mapUser(userCredential.user);
33
- }
34
- async signInWithGoogle(redirectTo) {
35
- await this.methods.signInWithPopup(this.auth, this.googleProvider);
36
- }
37
- async signOut() {
38
- await this.methods.signOut(this.auth);
39
- }
40
- onAuthStateChange(callback) {
41
- const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
42
- callback(this.mapUser(user));
43
- });
44
- return () => unsubscribe();
45
- }
46
- async getSessionToken() {
47
- if (!this.auth.currentUser)
48
- return null;
49
- try {
50
- return await this.auth.currentUser.getIdToken();
51
- }
52
- catch {
53
- return null;
54
- }
55
- }
56
- }
1
+ // Backwards compatibility for v1.1
2
+ export * from "./FirebaseWebClient";
3
+ export { FirebaseWebClient as FirebaseClient } from "./FirebaseWebClient";
57
4
  //# sourceMappingURL=FirebaseClient.js.map
@@ -0,0 +1,36 @@
1
+ import type { Auth, AuthCredential } from "firebase/auth";
2
+ import { AuthClient, User, AuthRuntime, SignInOptions, AuthCapabilities } from "../types";
3
+ export interface FirebaseNativeMethods {
4
+ signInWithEmailAndPassword: any;
5
+ signInWithCredential: any;
6
+ signOut: any;
7
+ onAuthStateChanged: any;
8
+ }
9
+ export interface FirebaseNativeClientOptions {
10
+ auth: Auth;
11
+ methods: FirebaseNativeMethods;
12
+ /**
13
+ * Callbacks that resolve an AuthCredential natively from a provider like Google or Apple
14
+ * e.g {"google": async (opts) => GoogleAuthProvider.credential(idToken)}
15
+ */
16
+ oauthHandlers?: Record<string, (options: SignInOptions) => Promise<AuthCredential>>;
17
+ }
18
+ export declare class FirebaseNativeClient implements AuthClient {
19
+ runtime: AuthRuntime;
20
+ private auth;
21
+ private methods;
22
+ private oauthHandlers;
23
+ constructor(options: FirebaseNativeClientOptions);
24
+ capabilities(): AuthCapabilities;
25
+ private mapUser;
26
+ getUser(): Promise<User | null>;
27
+ signInWithEmail(email: string, password: string): Promise<User>;
28
+ signIn(options: SignInOptions): Promise<void>;
29
+ /**
30
+ * @deprecated Config driven approach overrides this natively
31
+ */
32
+ signInWithGoogle(redirectTo?: string): Promise<void>;
33
+ signOut(): Promise<void>;
34
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
35
+ getSessionToken(): Promise<string | null>;
36
+ }
@@ -0,0 +1,88 @@
1
+ export class FirebaseNativeClient {
2
+ runtime = "native";
3
+ auth;
4
+ methods;
5
+ oauthHandlers;
6
+ constructor(options) {
7
+ this.auth = options.auth;
8
+ this.methods = options.methods;
9
+ this.oauthHandlers = options.oauthHandlers || {};
10
+ }
11
+ capabilities() {
12
+ return {
13
+ runtime: this.runtime,
14
+ supportedFlows: ["native"],
15
+ };
16
+ }
17
+ mapUser(user) {
18
+ if (!user)
19
+ return null;
20
+ return {
21
+ id: user.uid,
22
+ email: user.email || undefined,
23
+ avatarUrl: user.photoURL || undefined,
24
+ providerUserId: user.uid,
25
+ metadata: { displayName: user.displayName },
26
+ };
27
+ }
28
+ async getUser() {
29
+ if (this.auth.currentUser)
30
+ return this.mapUser(this.auth.currentUser);
31
+ return new Promise((resolve) => {
32
+ const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
33
+ unsubscribe();
34
+ resolve(this.mapUser(user));
35
+ });
36
+ });
37
+ }
38
+ async signInWithEmail(email, password) {
39
+ try {
40
+ const userCredential = await this.methods.signInWithEmailAndPassword(this.auth, email, password);
41
+ return this.mapUser(userCredential.user);
42
+ }
43
+ catch (error) {
44
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
45
+ }
46
+ }
47
+ async signIn(options) {
48
+ const provider = options.provider || "google";
49
+ if (!this.oauthHandlers[provider]) {
50
+ throw new Error(`CONFIG_ERROR: No oauthHandler defined for provider '${provider}' on FirebaseNativeClient`);
51
+ }
52
+ try {
53
+ // Native specific handling
54
+ const credential = await this.oauthHandlers[provider](options);
55
+ await this.methods.signInWithCredential(this.auth, credential);
56
+ }
57
+ catch (error) {
58
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
59
+ }
60
+ }
61
+ /**
62
+ * @deprecated Config driven approach overrides this natively
63
+ */
64
+ async signInWithGoogle(redirectTo) {
65
+ console.warn("signInWithGoogle is deprecated in favor of the unified signIn(...) API.");
66
+ return this.signIn({ provider: "google", flow: "native" });
67
+ }
68
+ async signOut() {
69
+ await this.methods.signOut(this.auth);
70
+ }
71
+ onAuthStateChange(callback) {
72
+ const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
73
+ callback(this.mapUser(user));
74
+ });
75
+ return () => unsubscribe();
76
+ }
77
+ async getSessionToken() {
78
+ if (!this.auth.currentUser)
79
+ return null;
80
+ try {
81
+ return await this.auth.currentUser.getIdToken();
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=FirebaseNativeClient.js.map
@@ -0,0 +1,29 @@
1
+ import type { Auth } from "firebase/auth";
2
+ import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
3
+ import { AuthClient, User, AuthRuntime, SignInOptions, AuthCapabilities } from "../types";
4
+ export interface FirebaseWebMethods {
5
+ signInWithEmailAndPassword: any;
6
+ signInWithPopup: any;
7
+ signInWithRedirect?: any;
8
+ signOut: any;
9
+ onAuthStateChanged: any;
10
+ }
11
+ export declare class FirebaseWebClient implements AuthClient {
12
+ private auth;
13
+ private methods;
14
+ private googleProvider;
15
+ runtime: AuthRuntime;
16
+ constructor(auth: Auth, methods: FirebaseWebMethods, googleProvider: GoogleAuthProviderType);
17
+ capabilities(): AuthCapabilities;
18
+ private mapUser;
19
+ getUser(): Promise<User | null>;
20
+ signInWithEmail(email: string, password: string): Promise<User>;
21
+ signIn(options: SignInOptions): Promise<void>;
22
+ /**
23
+ * @deprecated Use `signIn({ provider: "google", flow: "popup" })` instead
24
+ */
25
+ signInWithGoogle(redirectTo?: string): Promise<void>;
26
+ signOut(): Promise<void>;
27
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
28
+ getSessionToken(): Promise<string | null>;
29
+ }
@@ -0,0 +1,90 @@
1
+ export class FirebaseWebClient {
2
+ auth;
3
+ methods;
4
+ googleProvider;
5
+ runtime = "web";
6
+ constructor(auth, methods, googleProvider) {
7
+ this.auth = auth;
8
+ this.methods = methods;
9
+ this.googleProvider = googleProvider;
10
+ }
11
+ capabilities() {
12
+ return {
13
+ runtime: this.runtime,
14
+ supportedFlows: ["popup", "redirect"],
15
+ };
16
+ }
17
+ mapUser(user) {
18
+ if (!user)
19
+ return null;
20
+ return {
21
+ id: user.uid,
22
+ email: user.email || undefined,
23
+ avatarUrl: user.photoURL || undefined,
24
+ providerUserId: user.uid,
25
+ metadata: { displayName: user.displayName },
26
+ };
27
+ }
28
+ async getUser() {
29
+ if (this.auth.currentUser)
30
+ return this.mapUser(this.auth.currentUser);
31
+ return new Promise((resolve) => {
32
+ const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
33
+ unsubscribe();
34
+ resolve(this.mapUser(user));
35
+ });
36
+ });
37
+ }
38
+ async signInWithEmail(email, password) {
39
+ try {
40
+ const userCredential = await this.methods.signInWithEmailAndPassword(this.auth, email, password);
41
+ return this.mapUser(userCredential.user);
42
+ }
43
+ catch (error) {
44
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
45
+ }
46
+ }
47
+ async signIn(options) {
48
+ if (options.provider !== "google") {
49
+ throw new Error("CONFIG_ERROR: Currently only Google is implemented natively in this web adapter example");
50
+ }
51
+ try {
52
+ if (options.flow === "redirect") {
53
+ await this.methods.signInWithRedirect(this.auth, this.googleProvider);
54
+ }
55
+ else {
56
+ await this.methods.signInWithPopup(this.auth, this.googleProvider);
57
+ }
58
+ }
59
+ catch (error) {
60
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
61
+ }
62
+ }
63
+ /**
64
+ * @deprecated Use `signIn({ provider: "google", flow: "popup" })` instead
65
+ */
66
+ async signInWithGoogle(redirectTo) {
67
+ console.warn("signInWithGoogle is deprecated in favor of the unified signIn(...) API.");
68
+ return this.signIn({ provider: "google", flow: redirectTo ? "redirect" : "popup" });
69
+ }
70
+ async signOut() {
71
+ await this.methods.signOut(this.auth);
72
+ }
73
+ onAuthStateChange(callback) {
74
+ const unsubscribe = this.methods.onAuthStateChanged(this.auth, (user) => {
75
+ callback(this.mapUser(user));
76
+ });
77
+ return () => unsubscribe();
78
+ }
79
+ async getSessionToken() {
80
+ if (!this.auth.currentUser)
81
+ return null;
82
+ try {
83
+ return await this.auth.currentUser.getIdToken();
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ }
90
+ //# sourceMappingURL=FirebaseWebClient.js.map
@@ -1,29 +1,2 @@
1
- import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
2
- import type { Auth } from "firebase/auth";
3
- import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
4
- import { AuthClient, User } from "../types";
5
- export interface HybridFirebaseMethods {
6
- signInWithPopup: any;
7
- signOut: any;
8
- credentialFromResult: any;
9
- }
10
- export interface HybridClientOptions {
11
- supabase: SupabaseClientType;
12
- firebaseAuth: Auth | null;
13
- firebaseMethods: HybridFirebaseMethods | null;
14
- googleProvider: GoogleAuthProviderType | null;
15
- }
16
- export declare class HybridClient implements AuthClient {
17
- private supabase;
18
- private firebaseAuth;
19
- private methods;
20
- private googleProvider;
21
- constructor(options: HybridClientOptions);
22
- private mapUser;
23
- getUser(): Promise<User | null>;
24
- signInWithEmail(email: string, password: string): Promise<User>;
25
- signInWithGoogle(redirectTo?: string): Promise<void>;
26
- signOut(): Promise<void>;
27
- onAuthStateChange(callback: (user: User | null) => void): () => void;
28
- getSessionToken(): Promise<string | null>;
29
- }
1
+ export * from "./HybridWebClient";
2
+ export { HybridWebClient as HybridClient } from "./HybridWebClient";
@@ -1,108 +1,4 @@
1
- export class HybridClient {
2
- supabase;
3
- firebaseAuth;
4
- methods;
5
- googleProvider;
6
- constructor(options) {
7
- this.supabase = options.supabase;
8
- this.firebaseAuth = options.firebaseAuth;
9
- this.methods = options.firebaseMethods;
10
- this.googleProvider = options.googleProvider;
11
- }
12
- mapUser(user) {
13
- if (!user)
14
- return null;
15
- return {
16
- id: user.id,
17
- email: user.email,
18
- avatarUrl: user.user_metadata?.avatar_url || user.user_metadata?.picture,
19
- provider: user.app_metadata?.provider || 'supabase',
20
- metadata: user.user_metadata,
21
- };
22
- }
23
- async getUser() {
24
- const { data: { user }, error } = await this.supabase.auth.getUser();
25
- if (error && error.message.includes("session"))
26
- return null; // Safe fallback for unauthenticated
27
- if (error)
28
- throw error;
29
- return this.mapUser(user);
30
- }
31
- async signInWithEmail(email, password) {
32
- const { data, error } = await this.supabase.auth.signInWithPassword({
33
- email,
34
- password,
35
- });
36
- if (error)
37
- throw error;
38
- if (!data.user)
39
- throw new Error("No user returned");
40
- return this.mapUser(data.user);
41
- }
42
- async signInWithGoogle(redirectTo) {
43
- if (!this.firebaseAuth || !this.methods || !this.googleProvider) {
44
- console.warn("Firebase not configured, falling back to Supabase native OAuth");
45
- const { error } = await this.supabase.auth.signInWithOAuth({
46
- provider: "google",
47
- options: {
48
- redirectTo: redirectTo || (typeof window !== "undefined" ? window.location.origin : undefined),
49
- },
50
- });
51
- if (error)
52
- throw error;
53
- return;
54
- }
55
- try {
56
- // Firebase Google Popup
57
- const userCredential = await this.methods.signInWithPopup(this.firebaseAuth, this.googleProvider);
58
- // Generate Google OIDC ID token
59
- const credential = this.methods.credentialFromResult(userCredential);
60
- const idToken = credential?.idToken;
61
- if (!idToken)
62
- throw new Error("No Google ID Token found in credential");
63
- // Pass parallel ID Token directly into Supabase
64
- const { error: supaError } = await this.supabase.auth.signInWithIdToken({
65
- provider: 'google',
66
- token: idToken,
67
- });
68
- if (supaError)
69
- throw supaError;
70
- }
71
- catch (error) {
72
- // Ensure if anything fails, we clean up the floating firebase session
73
- if (this.firebaseAuth && this.methods) {
74
- await this.methods.signOut(this.firebaseAuth).catch(() => { });
75
- }
76
- throw error;
77
- }
78
- }
79
- async signOut() {
80
- // Break both sessions parallel safely
81
- if (this.firebaseAuth && this.methods) {
82
- try {
83
- await this.methods.signOut(this.firebaseAuth);
84
- }
85
- catch (e) {
86
- console.error("Firebase signout error:", e);
87
- }
88
- }
89
- const { error } = await this.supabase.auth.signOut();
90
- if (error)
91
- throw error;
92
- }
93
- onAuthStateChange(callback) {
94
- const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
95
- callback(this.mapUser(session?.user));
96
- });
97
- return () => {
98
- subscription.unsubscribe();
99
- };
100
- }
101
- async getSessionToken() {
102
- const { data: { session }, error } = await this.supabase.auth.getSession();
103
- if (error || !session)
104
- return null;
105
- return session.access_token;
106
- }
107
- }
1
+ // Backwards compatibility for v1.1
2
+ export * from "./HybridWebClient";
3
+ export { HybridWebClient as HybridClient } from "./HybridWebClient";
108
4
  //# sourceMappingURL=HybridClient.js.map
@@ -0,0 +1,40 @@
1
+ import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
2
+ import type { Auth, AuthCredential } from "firebase/auth";
3
+ import { AuthClient, User, AuthRuntime, SignInOptions, AuthCapabilities } from "../types";
4
+ export interface HybridNativeFirebaseMethods {
5
+ signInWithCredential: any;
6
+ signOut: any;
7
+ }
8
+ export interface HybridNativeClientOptions {
9
+ supabase: SupabaseClientType;
10
+ firebaseAuth: Auth | null;
11
+ firebaseMethods: HybridNativeFirebaseMethods | null;
12
+ /**
13
+ * Handlers for launching native OAuth flows using expo-auth-session or similar.
14
+ * Takes SignInOptions and returns a Firebase AuthCredential AND the raw idToken
15
+ */
16
+ oauthHandlers?: Record<string, (options: SignInOptions) => Promise<{
17
+ credential: AuthCredential;
18
+ idToken: string;
19
+ }>>;
20
+ }
21
+ export declare class HybridNativeClient implements AuthClient {
22
+ runtime: AuthRuntime;
23
+ private supabase;
24
+ private firebaseAuth;
25
+ private methods;
26
+ private oauthHandlers;
27
+ constructor(options: HybridNativeClientOptions);
28
+ capabilities(): AuthCapabilities;
29
+ private mapUser;
30
+ getUser(): Promise<User | null>;
31
+ signInWithEmail(email: string, password: string): Promise<User>;
32
+ signIn(options: SignInOptions): Promise<void>;
33
+ /**
34
+ * @deprecated Config driven approach overrides this natively
35
+ */
36
+ signInWithGoogle(redirectTo?: string): Promise<void>;
37
+ signOut(): Promise<void>;
38
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
39
+ getSessionToken(): Promise<string | null>;
40
+ }
@@ -0,0 +1,119 @@
1
+ export class HybridNativeClient {
2
+ runtime = "native";
3
+ supabase;
4
+ firebaseAuth;
5
+ methods;
6
+ oauthHandlers;
7
+ constructor(options) {
8
+ this.supabase = options.supabase;
9
+ this.firebaseAuth = options.firebaseAuth;
10
+ this.methods = options.firebaseMethods;
11
+ this.oauthHandlers = options.oauthHandlers || {};
12
+ }
13
+ capabilities() {
14
+ return {
15
+ runtime: this.runtime,
16
+ supportedFlows: ["native"],
17
+ };
18
+ }
19
+ mapUser(user) {
20
+ if (!user)
21
+ return null;
22
+ return {
23
+ id: user.id,
24
+ email: user.email,
25
+ avatarUrl: user.user_metadata?.avatar_url || user.user_metadata?.picture,
26
+ provider: user.app_metadata?.provider || 'supabase',
27
+ metadata: user.user_metadata,
28
+ };
29
+ }
30
+ async getUser() {
31
+ const { data: { user }, error } = await this.supabase.auth.getUser();
32
+ if (error && error.message.includes("session"))
33
+ return null;
34
+ if (error)
35
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
36
+ return this.mapUser(user);
37
+ }
38
+ async signInWithEmail(email, password) {
39
+ const { data, error } = await this.supabase.auth.signInWithPassword({
40
+ email,
41
+ password,
42
+ });
43
+ if (error)
44
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
45
+ if (!data.user)
46
+ throw new Error("SESSION_ERROR: No user returned");
47
+ return this.mapUser(data.user);
48
+ }
49
+ async signIn(options) {
50
+ const provider = options.provider || "google";
51
+ // Pure Supabase route
52
+ if (!this.firebaseAuth || !this.methods || !this.oauthHandlers[provider]) {
53
+ console.warn(`Native OAuth via Hybrid fallback for '${provider}' targeting Supabase purely`);
54
+ const { error } = await this.supabase.auth.signInWithOAuth({
55
+ provider: provider,
56
+ options: {
57
+ redirectTo: options.redirectUri,
58
+ skipBrowserRedirect: options.flow === "native",
59
+ },
60
+ });
61
+ if (error)
62
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
63
+ return;
64
+ }
65
+ try {
66
+ // Firebase route for generation -> Sync to Supabase
67
+ const { credential, idToken } = await this.oauthHandlers[provider](options);
68
+ // Setup Firebase
69
+ await this.methods.signInWithCredential(this.firebaseAuth, credential);
70
+ if (!idToken)
71
+ throw new Error("SESSION_ERROR: No ID Token found to sync to Supabase");
72
+ const { error: supaError } = await this.supabase.auth.signInWithIdToken({
73
+ provider: provider,
74
+ token: idToken,
75
+ });
76
+ if (supaError)
77
+ throw new Error(`PROVIDER_ERROR: ${supaError.message}`);
78
+ }
79
+ catch (error) {
80
+ if (this.firebaseAuth && this.methods) {
81
+ await this.methods.signOut(this.firebaseAuth).catch(() => { });
82
+ }
83
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
84
+ }
85
+ }
86
+ /**
87
+ * @deprecated Config driven approach overrides this natively
88
+ */
89
+ async signInWithGoogle(redirectTo) {
90
+ console.warn("signInWithGoogle is deprecated in favor of the unified signIn(...) API.");
91
+ return this.signIn({ provider: "google", flow: "native", redirectUri: redirectTo });
92
+ }
93
+ async signOut() {
94
+ if (this.firebaseAuth && this.methods) {
95
+ try {
96
+ await this.methods.signOut(this.firebaseAuth);
97
+ }
98
+ catch (e) {
99
+ console.error("Firebase signout error:", e);
100
+ }
101
+ }
102
+ const { error } = await this.supabase.auth.signOut();
103
+ if (error)
104
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
105
+ }
106
+ onAuthStateChange(callback) {
107
+ const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
108
+ callback(this.mapUser(session?.user));
109
+ });
110
+ return () => subscription.unsubscribe();
111
+ }
112
+ async getSessionToken() {
113
+ const { data: { session }, error } = await this.supabase.auth.getSession();
114
+ if (error || !session)
115
+ return null;
116
+ return session.access_token;
117
+ }
118
+ }
119
+ //# sourceMappingURL=HybridNativeClient.js.map
@@ -0,0 +1,36 @@
1
+ import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
2
+ import type { Auth } from "firebase/auth";
3
+ import type { GoogleAuthProvider as GoogleAuthProviderType } from "firebase/auth";
4
+ import { AuthClient, User, AuthRuntime, SignInOptions, AuthCapabilities } from "../types";
5
+ export interface HybridWebFirebaseMethods {
6
+ signInWithPopup: any;
7
+ signInWithRedirect?: any;
8
+ signOut: any;
9
+ credentialFromResult: any;
10
+ }
11
+ export interface HybridWebClientOptions {
12
+ supabase: SupabaseClientType;
13
+ firebaseAuth: Auth | null;
14
+ firebaseMethods: HybridWebFirebaseMethods | null;
15
+ googleProvider: GoogleAuthProviderType | null;
16
+ }
17
+ export declare class HybridWebClient implements AuthClient {
18
+ runtime: AuthRuntime;
19
+ private supabase;
20
+ private firebaseAuth;
21
+ private methods;
22
+ private googleProvider;
23
+ constructor(options: HybridWebClientOptions);
24
+ capabilities(): AuthCapabilities;
25
+ private mapUser;
26
+ getUser(): Promise<User | null>;
27
+ signInWithEmail(email: string, password: string): Promise<User>;
28
+ signIn(options: SignInOptions): Promise<void>;
29
+ /**
30
+ * @deprecated Use `signIn({ provider: "google", flow: "popup" })` instead
31
+ */
32
+ signInWithGoogle(redirectTo?: string): Promise<void>;
33
+ signOut(): Promise<void>;
34
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
35
+ getSessionToken(): Promise<string | null>;
36
+ }
@@ -0,0 +1,124 @@
1
+ export class HybridWebClient {
2
+ runtime = "web";
3
+ supabase;
4
+ firebaseAuth;
5
+ methods;
6
+ googleProvider;
7
+ constructor(options) {
8
+ this.supabase = options.supabase;
9
+ this.firebaseAuth = options.firebaseAuth;
10
+ this.methods = options.firebaseMethods;
11
+ this.googleProvider = options.googleProvider;
12
+ }
13
+ capabilities() {
14
+ return {
15
+ runtime: this.runtime,
16
+ supportedFlows: ["popup", "redirect"],
17
+ };
18
+ }
19
+ mapUser(user) {
20
+ if (!user)
21
+ return null;
22
+ return {
23
+ id: user.id,
24
+ email: user.email,
25
+ avatarUrl: user.user_metadata?.avatar_url || user.user_metadata?.picture,
26
+ provider: user.app_metadata?.provider || 'supabase',
27
+ metadata: user.user_metadata,
28
+ };
29
+ }
30
+ async getUser() {
31
+ const { data: { user }, error } = await this.supabase.auth.getUser();
32
+ if (error && error.message.includes("session"))
33
+ return null;
34
+ if (error)
35
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
36
+ return this.mapUser(user);
37
+ }
38
+ async signInWithEmail(email, password) {
39
+ const { data, error } = await this.supabase.auth.signInWithPassword({
40
+ email,
41
+ password,
42
+ });
43
+ if (error)
44
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
45
+ if (!data.user)
46
+ throw new Error("SESSION_ERROR: No user returned");
47
+ return this.mapUser(data.user);
48
+ }
49
+ async signIn(options) {
50
+ const provider = options.provider || "google";
51
+ if (!this.firebaseAuth || !this.methods || !this.googleProvider) {
52
+ console.warn("Firebase not configured on Hybrid fallback, using Supabase pure auth");
53
+ const { error } = await this.supabase.auth.signInWithOAuth({
54
+ provider: provider,
55
+ options: {
56
+ redirectTo: options.redirectUri || (typeof window !== "undefined" ? window.location.origin : undefined),
57
+ },
58
+ });
59
+ if (error)
60
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
61
+ return;
62
+ }
63
+ try {
64
+ let userCredential;
65
+ if (options.flow === "redirect") {
66
+ await this.methods.signInWithRedirect(this.firebaseAuth, this.googleProvider);
67
+ // In redirect flow, this acts differently (callback needs handling). Assuming popup for main.
68
+ return;
69
+ }
70
+ else {
71
+ userCredential = await this.methods.signInWithPopup(this.firebaseAuth, this.googleProvider);
72
+ }
73
+ const credential = this.methods.credentialFromResult(userCredential);
74
+ const idToken = credential?.idToken;
75
+ if (!idToken)
76
+ throw new Error("SESSION_ERROR: No Google ID Token found in credential");
77
+ const { error: supaError } = await this.supabase.auth.signInWithIdToken({
78
+ provider: provider,
79
+ token: idToken,
80
+ });
81
+ if (supaError)
82
+ throw new Error(`PROVIDER_ERROR: ${supaError.message}`);
83
+ }
84
+ catch (error) {
85
+ if (this.firebaseAuth && this.methods) {
86
+ await this.methods.signOut(this.firebaseAuth).catch(() => { });
87
+ }
88
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
89
+ }
90
+ }
91
+ /**
92
+ * @deprecated Use `signIn({ provider: "google", flow: "popup" })` instead
93
+ */
94
+ async signInWithGoogle(redirectTo) {
95
+ console.warn("signInWithGoogle is deprecated in favor of the unified signIn(...) API.");
96
+ return this.signIn({ provider: "google", flow: redirectTo ? "redirect" : "popup", redirectUri: redirectTo });
97
+ }
98
+ async signOut() {
99
+ if (this.firebaseAuth && this.methods) {
100
+ try {
101
+ await this.methods.signOut(this.firebaseAuth);
102
+ }
103
+ catch (e) {
104
+ console.error("Firebase signout error:", e);
105
+ }
106
+ }
107
+ const { error } = await this.supabase.auth.signOut();
108
+ if (error)
109
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
110
+ }
111
+ onAuthStateChange(callback) {
112
+ const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
113
+ callback(this.mapUser(session?.user));
114
+ });
115
+ return () => subscription.unsubscribe();
116
+ }
117
+ async getSessionToken() {
118
+ const { data: { session }, error } = await this.supabase.auth.getSession();
119
+ if (error || !session)
120
+ return null;
121
+ return session.access_token;
122
+ }
123
+ }
124
+ //# sourceMappingURL=HybridWebClient.js.map
@@ -1,11 +1,21 @@
1
1
  import type { SupabaseClient as SupabaseClientType } from "@supabase/supabase-js";
2
- import { AuthClient, User } from "../types";
2
+ import { AuthClient, User, AuthRuntime, SignInOptions, AuthCapabilities } from "../types";
3
+ export interface SupabaseClientOptions {
4
+ supabase: SupabaseClientType;
5
+ runtime?: AuthRuntime;
6
+ }
3
7
  export declare class SupabaseClient implements AuthClient {
8
+ runtime: AuthRuntime;
4
9
  private supabase;
5
- constructor(supabase: SupabaseClientType);
10
+ constructor(options: SupabaseClientOptions | SupabaseClientType);
11
+ capabilities(): AuthCapabilities;
6
12
  private mapUser;
7
13
  getUser(): Promise<User | null>;
8
14
  signInWithEmail(email: string, password: string): Promise<User>;
15
+ signIn(options: SignInOptions): Promise<void>;
16
+ /**
17
+ * @deprecated Use `signIn({ provider: "google", flow: "redirect", redirectUri })` instead
18
+ */
9
19
  signInWithGoogle(redirectTo?: string): Promise<void>;
10
20
  signOut(): Promise<void>;
11
21
  onAuthStateChange(callback: (user: User | null) => void): () => void;
@@ -1,7 +1,23 @@
1
1
  export class SupabaseClient {
2
+ runtime;
2
3
  supabase;
3
- constructor(supabase) {
4
- this.supabase = supabase;
4
+ constructor(options) {
5
+ // Handle backwards compatibility where just the client was passed
6
+ if ('auth' in options && !('supabase' in options)) {
7
+ this.supabase = options;
8
+ this.runtime = typeof window !== "undefined" ? "web" : "native"; // Best guess
9
+ }
10
+ else {
11
+ const opts = options;
12
+ this.supabase = opts.supabase;
13
+ this.runtime = opts.runtime || (typeof window !== "undefined" ? "web" : "native");
14
+ }
15
+ }
16
+ capabilities() {
17
+ return {
18
+ runtime: this.runtime,
19
+ supportedFlows: this.runtime === "web" ? ["redirect"] : ["native", "redirect"],
20
+ };
5
21
  }
6
22
  mapUser(user) {
7
23
  if (!user)
@@ -11,13 +27,14 @@ export class SupabaseClient {
11
27
  email: user.email,
12
28
  avatarUrl: user.user_metadata?.avatar_url,
13
29
  provider: user.app_metadata?.provider,
30
+ providerUserId: user.app_metadata?.provider_id || user.user_metadata?.provider_id,
14
31
  metadata: user.user_metadata,
15
32
  };
16
33
  }
17
34
  async getUser() {
18
35
  const { data: { user }, error } = await this.supabase.auth.getUser();
19
36
  if (error)
20
- throw error;
37
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
21
38
  return this.mapUser(user);
22
39
  }
23
40
  async signInWithEmail(email, password) {
@@ -26,25 +43,43 @@ export class SupabaseClient {
26
43
  password,
27
44
  });
28
45
  if (error)
29
- throw error;
46
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
30
47
  if (!data.user)
31
- throw new Error("No user returned");
48
+ throw new Error("SESSION_ERROR: No user returned");
32
49
  return this.mapUser(data.user);
33
50
  }
34
- async signInWithGoogle(redirectTo) {
35
- const { error } = await this.supabase.auth.signInWithOAuth({
36
- provider: "google",
51
+ async signIn(options) {
52
+ if (!options.provider) {
53
+ throw new Error("CONFIG_ERROR: options.provider is required for Supabase OAuth");
54
+ }
55
+ let redirectTo = options.redirectUri;
56
+ // Apply web assumption ONLY if strictly web
57
+ if (!redirectTo && this.runtime === "web" && typeof window !== "undefined") {
58
+ redirectTo = window.location.origin;
59
+ }
60
+ const { data, error } = await this.supabase.auth.signInWithOAuth({
61
+ provider: options.provider,
37
62
  options: {
38
- redirectTo: redirectTo || (typeof window !== "undefined" ? window.location.origin : undefined),
63
+ redirectTo,
64
+ skipBrowserRedirect: options.flow === "native",
39
65
  },
40
66
  });
41
67
  if (error)
42
- throw error;
68
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
69
+ // Return native callback URLs if strictly native flow without skipBrowserRedirect etc...
70
+ // For Expo users relying on deep-links, `skipBrowserRedirect` makes it return the URL to launch instead of redirecting the page.
71
+ }
72
+ /**
73
+ * @deprecated Use `signIn({ provider: "google", flow: "redirect", redirectUri })` instead
74
+ */
75
+ async signInWithGoogle(redirectTo) {
76
+ console.warn("signInWithGoogle is deprecated in favor of the unified signIn(...) API.");
77
+ return this.signIn({ provider: "google", flow: "redirect", redirectUri: redirectTo });
43
78
  }
44
79
  async signOut() {
45
80
  const { error } = await this.supabase.auth.signOut();
46
81
  if (error)
47
- throw error;
82
+ throw new Error(`PROVIDER_ERROR: ${error.message}`);
48
83
  }
49
84
  onAuthStateChange(callback) {
50
85
  const { data: { subscription } } = this.supabase.auth.onAuthStateChange((_event, session) => {
package/dist/types.d.ts CHANGED
@@ -1,15 +1,35 @@
1
+ export type AuthRuntime = "web" | "native" | "server";
2
+ export type OAuthFlow = "popup" | "redirect" | "native";
3
+ export interface SignInOptions {
4
+ provider?: "google" | "apple" | "github" | string;
5
+ flow?: OAuthFlow;
6
+ redirectUri?: string;
7
+ }
8
+ export type AuthErrorCode = "CONFIG_ERROR" | "UNSUPPORTED_FLOW" | "NETWORK_ERROR" | "PROVIDER_ERROR" | "SESSION_ERROR";
1
9
  export interface User {
2
10
  id: string;
3
11
  email?: string;
4
12
  avatarUrl?: string;
5
13
  provider?: string;
14
+ providerUserId?: string;
15
+ roles?: string[];
6
16
  metadata?: Record<string, any>;
7
17
  }
18
+ export interface AuthCapabilities {
19
+ runtime: AuthRuntime;
20
+ supportedFlows: OAuthFlow[];
21
+ }
8
22
  export interface AuthClient {
23
+ runtime: AuthRuntime;
9
24
  getUser(): Promise<User | null>;
10
25
  signInWithEmail(email: string, password: string): Promise<User>;
26
+ /**
27
+ * @deprecated Use `signIn({ provider: "google", flow: "popup" | "redirect" })` instead
28
+ */
11
29
  signInWithGoogle(redirectTo?: string): Promise<void>;
30
+ signIn(options: SignInOptions): Promise<void>;
12
31
  signOut(): Promise<void>;
13
32
  onAuthStateChange(callback: (user: User | null) => void): () => void;
14
33
  getSessionToken(): Promise<string | null>;
34
+ capabilities(): AuthCapabilities;
15
35
  }
package/package.json CHANGED
@@ -1,13 +1,49 @@
1
1
  {
2
2
  "name": "@edcalderon/auth",
3
- "version": "1.0.2",
4
- "description": "A universal, provider-agnostic authentication orchestration package with extensible adapters for Supabase, Firebase, Directus, and custom OAuth providers",
3
+ "version": "1.1.0",
4
+ "description": "A universal, provider-agnostic authentication package (Web + Next.js + Expo/React Native)",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js",
9
+ "require": "./dist/index.js",
10
+ "react-native": "./dist/index.js"
11
+ },
12
+ "./supabase": {
13
+ "types": "./dist/providers/SupabaseClient.d.ts",
14
+ "import": "./dist/providers/SupabaseClient.js",
15
+ "require": "./dist/providers/SupabaseClient.js",
16
+ "react-native": "./dist/providers/SupabaseClient.js"
17
+ },
18
+ "./firebase-web": {
19
+ "types": "./dist/providers/FirebaseWebClient.d.ts",
20
+ "import": "./dist/providers/FirebaseWebClient.js",
21
+ "require": "./dist/providers/FirebaseWebClient.js"
22
+ },
23
+ "./firebase-native": {
24
+ "types": "./dist/providers/FirebaseNativeClient.d.ts",
25
+ "import": "./dist/providers/FirebaseNativeClient.js",
26
+ "react-native": "./dist/providers/FirebaseNativeClient.js"
27
+ },
28
+ "./hybrid-web": {
29
+ "types": "./dist/providers/HybridWebClient.d.ts",
30
+ "import": "./dist/providers/HybridWebClient.js",
31
+ "require": "./dist/providers/HybridWebClient.js"
32
+ },
33
+ "./hybrid-native": {
34
+ "types": "./dist/providers/HybridNativeClient.d.ts",
35
+ "import": "./dist/providers/HybridNativeClient.js",
36
+ "react-native": "./dist/providers/HybridNativeClient.js"
37
+ }
38
+ },
5
39
  "main": "dist/index.js",
6
40
  "types": "dist/index.d.ts",
41
+ "sideEffects": false,
7
42
  "scripts": {
8
43
  "build": "tsc",
9
44
  "dev": "tsc --watch",
10
- "prepublishOnly": "npm run build"
45
+ "prepublishOnly": "npm run build",
46
+ "update-readme": "versioning update-readme"
11
47
  },
12
48
  "keywords": [
13
49
  "auth",
@@ -18,7 +54,9 @@
18
54
  "directus",
19
55
  "provider-agnostic",
20
56
  "react",
21
- "sso"
57
+ "sso",
58
+ "expo",
59
+ "react-native"
22
60
  ],
23
61
  "author": "Edward",
24
62
  "license": "MIT",
@@ -34,7 +72,8 @@
34
72
  "@supabase/supabase-js": "^2.0.0",
35
73
  "firebase": "^10.0.0 || ^11.0.0 || ^12.0.0",
36
74
  "react": "^18.0.0 || ^19.0.0",
37
- "react-dom": "^18.0.0 || ^19.0.0"
75
+ "react-dom": "^18.0.0 || ^19.0.0",
76
+ "react-native": "*"
38
77
  },
39
78
  "peerDependenciesMeta": {
40
79
  "@supabase/supabase-js": {
@@ -42,6 +81,12 @@
42
81
  },
43
82
  "firebase": {
44
83
  "optional": true
84
+ },
85
+ "react-dom": {
86
+ "optional": true
87
+ },
88
+ "react-native": {
89
+ "optional": true
45
90
  }
46
91
  },
47
92
  "devDependencies": {