@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 +8 -0
- package/README.md +13 -20
- package/dist/AuthProvider.d.ts +2 -0
- package/dist/AuthProvider.js +10 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +7 -3
- package/dist/providers/FirebaseClient.d.ts +2 -22
- package/dist/providers/FirebaseClient.js +3 -56
- package/dist/providers/FirebaseNativeClient.d.ts +36 -0
- package/dist/providers/FirebaseNativeClient.js +88 -0
- package/dist/providers/FirebaseWebClient.d.ts +29 -0
- package/dist/providers/FirebaseWebClient.js +90 -0
- package/dist/providers/HybridClient.d.ts +2 -29
- package/dist/providers/HybridClient.js +3 -107
- package/dist/providers/HybridNativeClient.d.ts +40 -0
- package/dist/providers/HybridNativeClient.js +119 -0
- package/dist/providers/HybridWebClient.d.ts +36 -0
- package/dist/providers/HybridWebClient.js +124 -0
- package/dist/providers/SupabaseClient.d.ts +12 -2
- package/dist/providers/SupabaseClient.js +46 -11
- package/dist/types.d.ts +20 -0
- package/package.json +50 -5
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.
|
|
11
|
+
## π Latest Changes (v1.0.3)
|
|
12
12
|
|
|
13
|
-
###
|
|
13
|
+
### Fixed
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
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
|
package/dist/AuthProvider.d.ts
CHANGED
|
@@ -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 }: {
|
package/dist/AuthProvider.js
CHANGED
|
@@ -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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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(
|
|
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(
|
|
4
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
4
|
-
"description": "A universal, provider-agnostic authentication
|
|
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": {
|