@smarthivelabs-devs/auth-expo 1.0.0 → 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/README.md +315 -247
- package/dist/index.d.ts +50 -12
- package/dist/index.js +73 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# @smarthivelabs-devs/auth-expo
|
|
2
2
|
|
|
3
|
-
SmartHive Auth for React Native and Expo. Provides a provider, hooks, and components with SecureStore-backed token storage
|
|
3
|
+
SmartHive Auth for React Native and Expo. Provides a provider, hooks, and components with SecureStore-backed token storage.
|
|
4
|
+
|
|
5
|
+
Supports two sign-in modes — both in the same package, zero config difference:
|
|
6
|
+
|
|
7
|
+
| Mode | How it works | Good for |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Headless** | Call `signIn.*` directly — no browser, custom UI | Native mobile apps with branded login screens |
|
|
10
|
+
| **OAuth redirect** | `login()` opens system browser, deep-links back | Social login, SSO, or when you want the hosted UI |
|
|
4
11
|
|
|
5
12
|
---
|
|
6
13
|
|
|
@@ -10,303 +17,351 @@ SmartHive Auth for React Native and Expo. Provides a provider, hooks, and compon
|
|
|
10
17
|
npx expo install @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store
|
|
11
18
|
```
|
|
12
19
|
|
|
13
|
-
Or with npm/pnpm (non-Expo managed workflow):
|
|
14
|
-
|
|
15
20
|
```bash
|
|
21
|
+
# npm / pnpm
|
|
16
22
|
npm install @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store
|
|
17
23
|
pnpm add @smarthivelabs-devs/auth-expo @smarthivelabs-devs/auth-sdk expo-auth-session expo-secure-store
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
> **Peer dependencies
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Prerequisites
|
|
25
|
-
|
|
26
|
-
1. A SmartHive Auth project — grab `projectId`, `publishableKey`, and `baseUrl` from your dashboard.
|
|
27
|
-
2. A registered deep link scheme for your Expo app (see below).
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Deep Link Setup
|
|
32
|
-
|
|
33
|
-
SmartHive Auth uses OAuth 2.0 + PKCE. After login, the server redirects back to your app via a deep link (e.g. `myapp://auth/callback`). You must register a custom scheme in `app.json`:
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
// app.json
|
|
37
|
-
{
|
|
38
|
-
"expo": {
|
|
39
|
-
"scheme": "myapp",
|
|
40
|
-
"android": {
|
|
41
|
-
"intentFilters": [
|
|
42
|
-
{
|
|
43
|
-
"action": "VIEW",
|
|
44
|
-
"autoVerify": true,
|
|
45
|
-
"data": [{ "scheme": "myapp" }],
|
|
46
|
-
"category": ["BROWSABLE", "DEFAULT"]
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Rebuild your native app after this change:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npx expo prebuild
|
|
58
|
-
```
|
|
26
|
+
> **Peer dependencies:** `expo-auth-session>=5`, `expo-secure-store>=12`, `react>=18`, `react-native>=0.73`
|
|
59
27
|
|
|
60
28
|
---
|
|
61
29
|
|
|
62
30
|
## Setup
|
|
63
31
|
|
|
64
|
-
Wrap your root component with `SmartHiveProvider`.
|
|
32
|
+
Wrap your root component with `SmartHiveProvider`. The `redirectUri` is only needed for the OAuth redirect flow — you can still set it even if you only use headless.
|
|
65
33
|
|
|
66
34
|
```tsx
|
|
67
|
-
//
|
|
35
|
+
// app/_layout.tsx
|
|
68
36
|
import { SmartHiveProvider, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";
|
|
69
37
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export default function App() {
|
|
38
|
+
export default function Layout() {
|
|
73
39
|
return (
|
|
74
40
|
<SmartHiveProvider
|
|
75
41
|
projectId={process.env.EXPO_PUBLIC_AUTH_PROJECT_ID!}
|
|
76
42
|
publishableKey={process.env.EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY!}
|
|
77
43
|
baseUrl={process.env.EXPO_PUBLIC_AUTH_BASE_URL!}
|
|
78
|
-
redirectUri={
|
|
44
|
+
redirectUri={buildRedirectUri("myapp")}
|
|
79
45
|
>
|
|
80
|
-
<
|
|
46
|
+
<Stack />
|
|
81
47
|
</SmartHiveProvider>
|
|
82
48
|
);
|
|
83
49
|
}
|
|
84
50
|
```
|
|
85
51
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
buildRedirectUri("myapp") // → "myapp://auth/callback"
|
|
92
|
-
buildRedirectUri("myapp", "auth/done") // → "myapp://auth/done"
|
|
52
|
+
```bash
|
|
53
|
+
# .env
|
|
54
|
+
EXPO_PUBLIC_AUTH_PROJECT_ID=proj_abc123
|
|
55
|
+
EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY=pk_prod_abc123
|
|
56
|
+
EXPO_PUBLIC_AUTH_BASE_URL=https://auth.myapp.com
|
|
93
57
|
```
|
|
94
58
|
|
|
95
59
|
---
|
|
96
60
|
|
|
97
|
-
##
|
|
61
|
+
## Headless Sign-in (Custom Login Screen)
|
|
98
62
|
|
|
99
|
-
|
|
100
|
-
|---|---|---|---|
|
|
101
|
-
| `projectId` | `string` | Yes | Your SmartHive project ID |
|
|
102
|
-
| `publishableKey` | `string` | Yes | Your publishable key |
|
|
103
|
-
| `baseUrl` | `string` | Yes | URL of your SmartHive Auth service |
|
|
104
|
-
| `redirectUri` | `string` | Yes | Deep link callback URI (e.g. `myapp://auth/callback`) |
|
|
105
|
-
| `authDomain` | `string` | No | Custom branded auth domain |
|
|
106
|
-
| `children` | `ReactNode` | Yes | Your app tree |
|
|
63
|
+
No browser, no redirect. Call the method, get tokens. Full control of your UI.
|
|
107
64
|
|
|
108
|
-
|
|
65
|
+
### Email + Password
|
|
109
66
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
3. The user logs in via the browser
|
|
115
|
-
4. The server redirects to `myapp://auth/callback?code=...&state=...`
|
|
116
|
-
5. `Linking` fires a `url` event — `SmartHiveProvider` handles it automatically, exchanges the code for tokens, and updates the session state
|
|
117
|
-
|
|
118
|
-
No manual callback handling is needed — the provider sets up the `Linking` event listener internally.
|
|
119
|
-
|
|
120
|
-
---
|
|
67
|
+
```tsx
|
|
68
|
+
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
69
|
+
import { useState } from "react";
|
|
70
|
+
import { Button, TextInput, View, Text } from "react-native";
|
|
121
71
|
|
|
122
|
-
|
|
72
|
+
export default function LoginScreen() {
|
|
73
|
+
const { signIn } = useAuth();
|
|
74
|
+
const [email, setEmail] = useState("");
|
|
75
|
+
const [password, setPassword] = useState("");
|
|
76
|
+
const [error, setError] = useState("");
|
|
77
|
+
|
|
78
|
+
async function handleSignIn() {
|
|
79
|
+
try {
|
|
80
|
+
await signIn.email({ email, password });
|
|
81
|
+
// Session is saved automatically — user is now signed in
|
|
82
|
+
} catch (e: any) {
|
|
83
|
+
setError(e.message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
123
86
|
|
|
124
|
-
|
|
87
|
+
return (
|
|
88
|
+
<View>
|
|
89
|
+
<TextInput value={email} onChangeText={setEmail} placeholder="Email" />
|
|
90
|
+
<TextInput value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry />
|
|
91
|
+
{error ? <Text>{error}</Text> : null}
|
|
92
|
+
<Button title="Sign in" onPress={handleSignIn} />
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
125
97
|
|
|
126
|
-
|
|
98
|
+
### Phone OTP
|
|
127
99
|
|
|
128
100
|
```tsx
|
|
129
101
|
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
102
|
+
import { useState } from "react";
|
|
103
|
+
import { Button, TextInput, View } from "react-native";
|
|
104
|
+
|
|
105
|
+
export default function PhoneLoginScreen() {
|
|
106
|
+
const { signIn } = useAuth();
|
|
107
|
+
const [phone, setPhone] = useState("");
|
|
108
|
+
const [code, setCode] = useState("");
|
|
109
|
+
const [step, setStep] = useState<"phone" | "code">("phone");
|
|
110
|
+
|
|
111
|
+
async function sendOtp() {
|
|
112
|
+
await signIn.phone.sendOtp({ phoneNumber: phone });
|
|
113
|
+
setStep("code");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function verifyOtp() {
|
|
117
|
+
await signIn.phone.verify({ phoneNumber: phone, code });
|
|
118
|
+
// Signed in — session saved automatically
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (step === "phone") {
|
|
122
|
+
return (
|
|
123
|
+
<View>
|
|
124
|
+
<TextInput value={phone} onChangeText={setPhone} placeholder="+1234567890" keyboardType="phone-pad" />
|
|
125
|
+
<Button title="Send code" onPress={sendOtp} />
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
login, // (options?) => Promise<void>
|
|
137
|
-
logout, // () => Promise<void>
|
|
138
|
-
refreshSession, // () => Promise<void>
|
|
139
|
-
authFetch, // authenticated fetch wrapper
|
|
140
|
-
} = useAuth();
|
|
141
|
-
|
|
142
|
-
if (!isLoaded) return <ActivityIndicator />;
|
|
143
|
-
|
|
144
|
-
return isSignedIn ? (
|
|
145
|
-
<Button title="Sign out" onPress={logout} />
|
|
146
|
-
) : (
|
|
147
|
-
<Button title="Sign in" onPress={() => login()} />
|
|
130
|
+
return (
|
|
131
|
+
<View>
|
|
132
|
+
<TextInput value={code} onChangeText={setCode} placeholder="Enter code" keyboardType="number-pad" />
|
|
133
|
+
<Button title="Verify" onPress={verifyOtp} />
|
|
134
|
+
</View>
|
|
148
135
|
);
|
|
149
136
|
}
|
|
150
137
|
```
|
|
151
138
|
|
|
152
|
-
|
|
139
|
+
### Email OTP
|
|
153
140
|
|
|
154
|
-
|
|
141
|
+
```tsx
|
|
142
|
+
const { signIn } = useAuth();
|
|
143
|
+
|
|
144
|
+
// Step 1 — send the code
|
|
145
|
+
await signIn.emailOtp.send({ email: "user@example.com" });
|
|
146
|
+
|
|
147
|
+
// Step 2 — verify (returns session, user is now signed in)
|
|
148
|
+
await signIn.emailOtp.verify({ email: "user@example.com", code: "123456" });
|
|
149
|
+
```
|
|
155
150
|
|
|
156
|
-
|
|
151
|
+
### Magic Link
|
|
157
152
|
|
|
158
153
|
```tsx
|
|
159
|
-
|
|
154
|
+
const { signIn } = useAuth();
|
|
160
155
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (!session) return <Text>Not signed in</Text>;
|
|
164
|
-
return <Text selectable>{session.accessToken}</Text>;
|
|
165
|
-
}
|
|
156
|
+
// Sends an email — user clicks the link to sign in (no token returned here)
|
|
157
|
+
await signIn.magicLink.send({ email: "user@example.com" });
|
|
166
158
|
```
|
|
167
159
|
|
|
168
160
|
---
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Returns the user object from the session, or `null`.
|
|
162
|
+
## Headless Sign-up
|
|
173
163
|
|
|
174
164
|
```tsx
|
|
175
|
-
import {
|
|
165
|
+
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
166
|
+
|
|
167
|
+
const { signUp } = useAuth();
|
|
168
|
+
|
|
169
|
+
const result = await signUp.email({
|
|
170
|
+
email: "user@example.com",
|
|
171
|
+
password: "secret123",
|
|
172
|
+
name: "Jane Doe", // optional
|
|
173
|
+
});
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
if (result.requiresVerification) {
|
|
176
|
+
// Email verification required — show "check your inbox" screen
|
|
177
|
+
// No session yet, tokens are empty
|
|
178
|
+
} else {
|
|
179
|
+
// Account created and signed in immediately
|
|
181
180
|
}
|
|
182
181
|
```
|
|
183
182
|
|
|
184
183
|
---
|
|
185
184
|
|
|
186
|
-
|
|
185
|
+
## Social OAuth Sign-in (Google, Apple, GitHub, etc.)
|
|
187
186
|
|
|
188
|
-
|
|
187
|
+
Each social provider uses **your project's own credentials** — the consent screen shows your app name. Configure credentials in your SmartHive dashboard under **Project → OAuth Providers**, then call:
|
|
189
188
|
|
|
190
189
|
```tsx
|
|
191
|
-
import {
|
|
190
|
+
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
191
|
+
import { Button } from "react-native";
|
|
192
|
+
|
|
193
|
+
export default function LoginScreen() {
|
|
194
|
+
const { signIn } = useAuth();
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
return (
|
|
197
|
+
<>
|
|
198
|
+
<Button title="Continue with Google" onPress={() => signIn.social("google")} />
|
|
199
|
+
<Button title="Continue with Apple" onPress={() => signIn.social("apple")} />
|
|
200
|
+
<Button title="Continue with GitHub" onPress={() => signIn.social("github")} />
|
|
201
|
+
</>
|
|
202
|
+
);
|
|
197
203
|
}
|
|
198
204
|
```
|
|
199
205
|
|
|
206
|
+
`signIn.social()` calls `Linking.openURL()` to open the provider's consent screen in the system browser. When the user approves, the provider redirects back to your app's `redirectUri` deep link with `?access_token=...&refresh_token=...`. The `SmartHiveProvider` deep link listener picks this up automatically and saves the session — no extra setup needed.
|
|
207
|
+
|
|
208
|
+
**Supported providers:**
|
|
209
|
+
`google` · `apple` · `github` · `facebook` · `twitter` · `linkedin` · `microsoft` · `discord` · `spotify` · `twitch` · `reddit` · `gitlab` · `slack` · `notion` · `zoom` · `figma`
|
|
210
|
+
|
|
211
|
+
> **Deep link required** — make sure `redirectUri` is set to your app scheme (e.g. `myapp://auth/callback`) and your scheme is registered in `app.json`. See the Deep Link Setup section below.
|
|
212
|
+
|
|
200
213
|
---
|
|
201
214
|
|
|
202
|
-
|
|
215
|
+
## OAuth Redirect Sign-in (SmartHive hosted page)
|
|
203
216
|
|
|
204
|
-
|
|
217
|
+
The original PKCE flow — redirects to the SmartHive hosted login page and back. Use it for SSO or when you want the hosted login UI.
|
|
205
218
|
|
|
206
219
|
```tsx
|
|
207
|
-
import {
|
|
220
|
+
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
221
|
+
import { Button } from "react-native";
|
|
222
|
+
|
|
223
|
+
export default function LoginScreen() {
|
|
224
|
+
const { login } = useAuth();
|
|
225
|
+
return <Button title="Sign in with SmartHive" onPress={() => login()} />;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
208
228
|
|
|
209
|
-
|
|
210
|
-
const isSignedIn = useIsSignedIn();
|
|
211
|
-
const router = useRouter();
|
|
229
|
+
### Deep Link Setup (required for OAuth redirect only)
|
|
212
230
|
|
|
213
|
-
|
|
214
|
-
if (isSignedIn === false) router.replace("/login");
|
|
215
|
-
}, [isSignedIn]);
|
|
231
|
+
If you use `login()`, register a custom scheme in `app.json` so the browser can redirect back:
|
|
216
232
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"expo": {
|
|
236
|
+
"scheme": "myapp",
|
|
237
|
+
"android": {
|
|
238
|
+
"intentFilters": [
|
|
239
|
+
{
|
|
240
|
+
"action": "VIEW",
|
|
241
|
+
"autoVerify": true,
|
|
242
|
+
"data": [{ "scheme": "myapp" }],
|
|
243
|
+
"category": ["BROWSABLE", "DEFAULT"]
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
220
248
|
}
|
|
221
249
|
```
|
|
222
250
|
|
|
223
|
-
|
|
251
|
+
Rebuild after changing `app.json`:
|
|
224
252
|
|
|
225
|
-
|
|
253
|
+
```bash
|
|
254
|
+
npx expo prebuild
|
|
255
|
+
```
|
|
226
256
|
|
|
227
|
-
|
|
257
|
+
No extra setup needed for headless sign-in — it works without a deep link.
|
|
228
258
|
|
|
229
|
-
|
|
230
|
-
import { useAuthFetch } from "@smarthivelabs-devs/auth-expo";
|
|
259
|
+
---
|
|
231
260
|
|
|
232
|
-
|
|
233
|
-
const authFetch = useAuthFetch();
|
|
234
|
-
const [data, setData] = useState(null);
|
|
261
|
+
## Sign-out
|
|
235
262
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
setData(await res.json());
|
|
239
|
-
}
|
|
263
|
+
```tsx
|
|
264
|
+
const { logout } = useAuth();
|
|
240
265
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<Button title="Load data" onPress={load} />
|
|
244
|
-
{data && <Text>{JSON.stringify(data)}</Text>}
|
|
245
|
-
</View>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
266
|
+
// Clears SecureStore + invalidates session on the server
|
|
267
|
+
await logout();
|
|
248
268
|
```
|
|
249
269
|
|
|
250
270
|
---
|
|
251
271
|
|
|
252
|
-
|
|
272
|
+
## Hooks
|
|
273
|
+
|
|
274
|
+
### `useAuth()`
|
|
253
275
|
|
|
254
|
-
Returns
|
|
276
|
+
Returns the full auth context.
|
|
255
277
|
|
|
256
278
|
```tsx
|
|
257
|
-
|
|
279
|
+
const {
|
|
280
|
+
session, // AuthSession | null
|
|
281
|
+
isLoaded, // true once initial SecureStore read is done
|
|
282
|
+
isSignedIn, // boolean
|
|
283
|
+
login, // OAuth redirect sign-in (PKCE, SmartHive hosted page)
|
|
284
|
+
logout, // sign out
|
|
285
|
+
signIn, // headless + social sign-in methods
|
|
286
|
+
signUp, // headless sign-up methods
|
|
287
|
+
refreshSession, // force a token refresh
|
|
288
|
+
authFetch, // authenticated fetch wrapper
|
|
289
|
+
getAuthorizationHeader, // () => Promise<{ authorization: string }>
|
|
290
|
+
} = useAuth();
|
|
291
|
+
|
|
292
|
+
// Social sign-in is under signIn.social:
|
|
293
|
+
await signIn.social("google");
|
|
294
|
+
await signIn.social("apple");
|
|
295
|
+
await signIn.social("github");
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `useSession()`
|
|
258
299
|
|
|
259
|
-
|
|
260
|
-
|
|
300
|
+
```tsx
|
|
301
|
+
const session = useSession(); // AuthSession | null
|
|
302
|
+
// session.accessToken, session.refreshToken, session.expiresAt, session.user
|
|
303
|
+
```
|
|
261
304
|
|
|
262
|
-
|
|
263
|
-
const headers = await getAuthorizationHeader();
|
|
264
|
-
operation.setContext({ headers });
|
|
265
|
-
return forward(operation);
|
|
266
|
-
});
|
|
305
|
+
### `useUser()`
|
|
267
306
|
|
|
268
|
-
|
|
269
|
-
|
|
307
|
+
```tsx
|
|
308
|
+
const user = useUser(); // unknown | null
|
|
270
309
|
```
|
|
271
310
|
|
|
272
|
-
|
|
311
|
+
### `useIsLoaded()`
|
|
273
312
|
|
|
274
|
-
|
|
313
|
+
`true` once the initial SecureStore check is complete. Use this to avoid a flash of unauthenticated state on startup.
|
|
275
314
|
|
|
276
|
-
|
|
315
|
+
```tsx
|
|
316
|
+
const isLoaded = useIsLoaded();
|
|
317
|
+
if (!isLoaded) return <SplashScreen />;
|
|
318
|
+
```
|
|
277
319
|
|
|
278
|
-
|
|
320
|
+
### `useIsSignedIn()`
|
|
321
|
+
|
|
322
|
+
Returns `true` (signed in), `false` (signed out), or `null` (still loading).
|
|
279
323
|
|
|
280
324
|
```tsx
|
|
281
|
-
|
|
325
|
+
const isSignedIn = useIsSignedIn();
|
|
282
326
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (isSignedIn === false) router.replace("/login");
|
|
329
|
+
}, [isSignedIn]);
|
|
286
330
|
```
|
|
287
331
|
|
|
288
|
-
###
|
|
332
|
+
### `useAuthFetch()`
|
|
289
333
|
|
|
290
|
-
|
|
334
|
+
Authenticated `fetch` wrapper. Bearer token is injected automatically and refreshed transparently when near expiry.
|
|
291
335
|
|
|
292
336
|
```tsx
|
|
293
|
-
|
|
337
|
+
const authFetch = useAuthFetch();
|
|
338
|
+
const res = await authFetch("https://api.myapp.com/protected");
|
|
339
|
+
```
|
|
294
340
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
341
|
+
### `useAuthorizationHeader()`
|
|
342
|
+
|
|
343
|
+
Resolves to `{ authorization: "Bearer <token>" }`. Useful for GraphQL clients or custom SDK setup.
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
const getAuthorizationHeader = useAuthorizationHeader();
|
|
347
|
+
const headers = await getAuthorizationHeader();
|
|
298
348
|
```
|
|
299
349
|
|
|
300
|
-
|
|
350
|
+
---
|
|
301
351
|
|
|
302
|
-
|
|
352
|
+
## Render Helpers
|
|
303
353
|
|
|
304
354
|
```tsx
|
|
305
|
-
import { AuthLoading } from "@smarthivelabs-devs/auth-expo";
|
|
355
|
+
import { SignedIn, SignedOut, AuthLoading } from "@smarthivelabs-devs/auth-expo";
|
|
356
|
+
|
|
357
|
+
// Shown only when loaded + authenticated
|
|
358
|
+
<SignedIn><Dashboard /></SignedIn>
|
|
306
359
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
360
|
+
// Shown only when loaded + not authenticated
|
|
361
|
+
<SignedOut><LoginScreen /></SignedOut>
|
|
362
|
+
|
|
363
|
+
// Shown while the initial SecureStore check is running
|
|
364
|
+
<AuthLoading><ActivityIndicator /></AuthLoading>
|
|
310
365
|
```
|
|
311
366
|
|
|
312
367
|
---
|
|
@@ -315,11 +370,11 @@ import { AuthLoading } from "@smarthivelabs-devs/auth-expo";
|
|
|
315
370
|
|
|
316
371
|
```
|
|
317
372
|
app/
|
|
318
|
-
├── _layout.tsx
|
|
319
|
-
├── index.tsx
|
|
320
|
-
├── login.tsx
|
|
373
|
+
├── _layout.tsx ← SmartHiveProvider here
|
|
374
|
+
├── index.tsx ← SignedIn / SignedOut routing
|
|
375
|
+
├── login.tsx ← your custom login screen using signIn.*
|
|
321
376
|
└── (protected)/
|
|
322
|
-
└── dashboard.tsx
|
|
377
|
+
└── dashboard.tsx
|
|
323
378
|
```
|
|
324
379
|
|
|
325
380
|
```tsx
|
|
@@ -359,85 +414,70 @@ export default function Index() {
|
|
|
359
414
|
```
|
|
360
415
|
|
|
361
416
|
```tsx
|
|
362
|
-
// app/login.tsx
|
|
417
|
+
// app/login.tsx — custom screen, no browser redirect
|
|
363
418
|
import { useAuth } from "@smarthivelabs-devs/auth-expo";
|
|
364
|
-
import {
|
|
419
|
+
import { useState } from "react";
|
|
420
|
+
import { Button, TextInput, View, Text, StyleSheet } from "react-native";
|
|
365
421
|
|
|
366
422
|
export default function LoginScreen() {
|
|
367
|
-
const {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
423
|
+
const { signIn } = useAuth();
|
|
424
|
+
const [email, setEmail] = useState("");
|
|
425
|
+
const [password, setPassword] = useState("");
|
|
426
|
+
const [loading, setLoading] = useState(false);
|
|
427
|
+
const [error, setError] = useState("");
|
|
428
|
+
|
|
429
|
+
async function handleSignIn() {
|
|
430
|
+
setLoading(true);
|
|
431
|
+
setError("");
|
|
432
|
+
try {
|
|
433
|
+
await signIn.email({ email, password });
|
|
434
|
+
} catch (e: any) {
|
|
435
|
+
setError(e.message ?? "Sign in failed.");
|
|
436
|
+
} finally {
|
|
437
|
+
setLoading(false);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
383
440
|
|
|
384
441
|
return (
|
|
385
|
-
<
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
</
|
|
442
|
+
<View style={styles.container}>
|
|
443
|
+
<TextInput style={styles.input} value={email} onChangeText={setEmail} placeholder="Email" autoCapitalize="none" />
|
|
444
|
+
<TextInput style={styles.input} value={password} onChangeText={setPassword} placeholder="Password" secureTextEntry />
|
|
445
|
+
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
446
|
+
<Button title={loading ? "Signing in…" : "Sign in"} onPress={handleSignIn} disabled={loading} />
|
|
447
|
+
</View>
|
|
391
448
|
);
|
|
392
449
|
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
---
|
|
396
|
-
|
|
397
|
-
## Low-level: `initExpoAuth`
|
|
398
|
-
|
|
399
|
-
If you need the client directly without the provider (advanced use), use `initExpoAuth`:
|
|
400
|
-
|
|
401
|
-
```ts
|
|
402
|
-
import { initExpoAuth, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";
|
|
403
450
|
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
redirectUri: buildRedirectUri("myapp"),
|
|
451
|
+
const styles = StyleSheet.create({
|
|
452
|
+
container: { flex: 1, justifyContent: "center", padding: 24 },
|
|
453
|
+
input: { borderWidth: 1, borderColor: "#ccc", borderRadius: 8, padding: 12, marginBottom: 12 },
|
|
454
|
+
error: { color: "red", marginBottom: 12 },
|
|
409
455
|
});
|
|
410
|
-
|
|
411
|
-
await client.initialize();
|
|
412
|
-
|
|
413
|
-
// The client behaves the same as SmartHiveAuthClient from auth-sdk,
|
|
414
|
-
// but uses SecureStore and Linking instead of localStorage + location.assign
|
|
415
456
|
```
|
|
416
457
|
|
|
417
458
|
---
|
|
418
459
|
|
|
419
|
-
##
|
|
460
|
+
## Token Storage
|
|
420
461
|
|
|
421
|
-
|
|
462
|
+
All tokens are stored in `expo-secure-store`:
|
|
422
463
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
EXPO_PUBLIC_AUTH_PROJECT_ID=proj_abc123
|
|
426
|
-
EXPO_PUBLIC_AUTH_PUBLISHABLE_KEY=pk_live_abc123
|
|
427
|
-
EXPO_PUBLIC_AUTH_BASE_URL=https://auth.myapp.com
|
|
428
|
-
```
|
|
464
|
+
- **iOS**: Keychain Services
|
|
465
|
+
- **Android**: Android Keystore (AES encryption)
|
|
429
466
|
|
|
430
|
-
|
|
467
|
+
PKCE verifier and state (used during OAuth redirect flow) are also stored in SecureStore and deleted after the code exchange completes.
|
|
431
468
|
|
|
432
469
|
---
|
|
433
470
|
|
|
434
|
-
##
|
|
435
|
-
|
|
436
|
-
Tokens are stored in `expo-secure-store`, which uses:
|
|
437
|
-
- **iOS**: Keychain Services
|
|
438
|
-
- **Android**: Keystore System (Android Keystore-backed AES encryption)
|
|
471
|
+
## Provider Props
|
|
439
472
|
|
|
440
|
-
|
|
473
|
+
| Prop | Type | Required | Description |
|
|
474
|
+
|---|---|---|---|
|
|
475
|
+
| `projectId` | `string` | Yes | Your SmartHive project ID |
|
|
476
|
+
| `publishableKey` | `string` | Yes | Your publishable key (`pk_prod_*`) |
|
|
477
|
+
| `baseUrl` | `string` | Yes | URL of your SmartHive Auth service |
|
|
478
|
+
| `redirectUri` | `string` | Yes | Deep link callback URI — used only for OAuth redirect flow |
|
|
479
|
+
| `authDomain` | `string` | No | Custom branded auth domain |
|
|
480
|
+
| `children` | `ReactNode` | Yes | Your app tree |
|
|
441
481
|
|
|
442
482
|
---
|
|
443
483
|
|
|
@@ -451,12 +491,40 @@ import type {
|
|
|
451
491
|
|
|
452
492
|
import type {
|
|
453
493
|
AuthSession,
|
|
494
|
+
HeadlessClient,
|
|
495
|
+
HeadlessSignInResult,
|
|
496
|
+
HeadlessSignUpResult,
|
|
454
497
|
SmartHiveAuthClient,
|
|
455
498
|
} from "@smarthivelabs-devs/auth-sdk";
|
|
456
499
|
```
|
|
457
500
|
|
|
458
501
|
---
|
|
459
502
|
|
|
503
|
+
## Low-level: `initExpoAuth`
|
|
504
|
+
|
|
505
|
+
Direct client without the provider (advanced use):
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
import { initExpoAuth, buildRedirectUri } from "@smarthivelabs-devs/auth-expo";
|
|
509
|
+
|
|
510
|
+
const client = initExpoAuth({
|
|
511
|
+
projectId: "proj_abc123",
|
|
512
|
+
publishableKey: "pk_prod_abc123",
|
|
513
|
+
baseUrl: "https://auth.myapp.com",
|
|
514
|
+
redirectUri: buildRedirectUri("myapp"),
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
await client.initialize();
|
|
518
|
+
|
|
519
|
+
// Headless sign-in
|
|
520
|
+
const session = await client.headless.signIn.email({ email, password });
|
|
521
|
+
|
|
522
|
+
// OAuth redirect
|
|
523
|
+
await client.login();
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
460
528
|
## Related Packages
|
|
461
529
|
|
|
462
530
|
| Package | Use case |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { SmartHiveAuthConfig, SmartHiveAuthClient, AuthSession } from '@smarthivelabs-devs/auth-sdk';
|
|
2
|
+
import { SmartHiveAuthConfig, SmartHiveAuthClient, AuthSession, HeadlessSignInResult, SocialProvider, HeadlessSignUpResult } from '@smarthivelabs-devs/auth-sdk';
|
|
3
3
|
|
|
4
4
|
interface SmartHiveExpoConfig extends Omit<SmartHiveAuthConfig, "storage"> {
|
|
5
5
|
/** Deep link redirect URI, e.g. "myapp://auth/callback" */
|
|
@@ -20,6 +20,7 @@ interface AuthContextValue {
|
|
|
20
20
|
session: AuthSession | null;
|
|
21
21
|
isLoaded: boolean;
|
|
22
22
|
isSignedIn: boolean;
|
|
23
|
+
/** OAuth 2.0 + PKCE browser redirect sign-in (unchanged). */
|
|
23
24
|
login(options?: {
|
|
24
25
|
redirectUri?: string;
|
|
25
26
|
}): Promise<void>;
|
|
@@ -27,23 +28,60 @@ interface AuthContextValue {
|
|
|
27
28
|
refreshSession(): Promise<void>;
|
|
28
29
|
getAuthorizationHeader(): Promise<Record<string, string>>;
|
|
29
30
|
authFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
31
|
+
/**
|
|
32
|
+
* Headless (no browser) sign-in methods for custom login screens.
|
|
33
|
+
* Tokens are stored in SecureStore automatically on success.
|
|
34
|
+
*/
|
|
35
|
+
signIn: {
|
|
36
|
+
email(params: {
|
|
37
|
+
email: string;
|
|
38
|
+
password: string;
|
|
39
|
+
}): Promise<HeadlessSignInResult>;
|
|
40
|
+
phone: {
|
|
41
|
+
sendOtp(params: {
|
|
42
|
+
phoneNumber: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
verify(params: {
|
|
45
|
+
phoneNumber: string;
|
|
46
|
+
code: string;
|
|
47
|
+
}): Promise<HeadlessSignInResult>;
|
|
48
|
+
};
|
|
49
|
+
emailOtp: {
|
|
50
|
+
send(params: {
|
|
51
|
+
email: string;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
verify(params: {
|
|
54
|
+
email: string;
|
|
55
|
+
code: string;
|
|
56
|
+
}): Promise<HeadlessSignInResult>;
|
|
57
|
+
};
|
|
58
|
+
magicLink: {
|
|
59
|
+
send(params: {
|
|
60
|
+
email: string;
|
|
61
|
+
callbackURL?: string;
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
};
|
|
64
|
+
/** Initiate a social OAuth flow — opens the provider's consent screen via Linking. */
|
|
65
|
+
social(provider: SocialProvider, options?: {
|
|
66
|
+
redirectUri?: string;
|
|
67
|
+
}): Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
signUp: {
|
|
70
|
+
email(params: {
|
|
71
|
+
email: string;
|
|
72
|
+
password: string;
|
|
73
|
+
name?: string;
|
|
74
|
+
}): Promise<HeadlessSignUpResult>;
|
|
75
|
+
};
|
|
30
76
|
}
|
|
31
77
|
interface SmartHiveProviderProps extends SmartHiveExpoConfig {
|
|
32
78
|
children: React.ReactNode;
|
|
33
79
|
}
|
|
34
80
|
/**
|
|
35
81
|
* Wraps your Expo app and provides auth state to all child components.
|
|
36
|
-
* Tokens are stored in SecureStore.
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* <SmartHiveProvider
|
|
40
|
-
* projectId="proj_..."
|
|
41
|
-
* publishableKey="pk_..."
|
|
42
|
-
* baseUrl="https://authcore.smarthivelabs.dev/prod"
|
|
43
|
-
* redirectUri="myapp://auth/callback"
|
|
44
|
-
* >
|
|
45
|
-
* <App />
|
|
46
|
-
* </SmartHiveProvider>
|
|
82
|
+
* Tokens are stored in SecureStore. Supports both:
|
|
83
|
+
* - OAuth 2.0 + PKCE browser redirect via `login()`
|
|
84
|
+
* - Headless direct sign-in via `signIn.*` (no browser, custom login screens)
|
|
47
85
|
*/
|
|
48
86
|
declare function SmartHiveProvider({ children, ...config }: SmartHiveProviderProps): react_jsx_runtime.JSX.Element;
|
|
49
87
|
declare function useAuth(): AuthContextValue;
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,8 @@ import { Linking } from "react-native";
|
|
|
12
12
|
import { AuthRequest } from "expo-auth-session";
|
|
13
13
|
import * as SecureStore from "expo-secure-store";
|
|
14
14
|
import {
|
|
15
|
-
initAuth
|
|
15
|
+
initAuth,
|
|
16
|
+
envFromPublishableKey
|
|
16
17
|
} from "@smarthivelabs-devs/auth-sdk";
|
|
17
18
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
18
19
|
var secureStorage = {
|
|
@@ -60,6 +61,15 @@ function initExpoAuth(config) {
|
|
|
60
61
|
}
|
|
61
62
|
await secureStorage.setItem(storageKeys.pkceState, request.state);
|
|
62
63
|
await Linking.openURL(url);
|
|
64
|
+
},
|
|
65
|
+
async loginSocial(provider, options) {
|
|
66
|
+
const redirectUri = options?.redirectUri ?? config.redirectUri;
|
|
67
|
+
const authBase = normalizeBaseUrl(config.authDomain ?? config.baseUrl);
|
|
68
|
+
const env = envFromPublishableKey(config.publishableKey);
|
|
69
|
+
const url = new URL(`${authBase}/${env}/api/auth/social/${provider}`);
|
|
70
|
+
url.searchParams.set("project_id", config.projectId);
|
|
71
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
72
|
+
await Linking.openURL(url.toString());
|
|
63
73
|
}
|
|
64
74
|
};
|
|
65
75
|
}
|
|
@@ -83,8 +93,17 @@ function SmartHiveProvider({ children, ...config }) {
|
|
|
83
93
|
}, [client]);
|
|
84
94
|
useEffect(() => {
|
|
85
95
|
function handleUrl({ url }) {
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
try {
|
|
97
|
+
const parsed = new URL(url);
|
|
98
|
+
if (parsed.searchParams.has("access_token") || parsed.searchParams.has("error")) {
|
|
99
|
+
client.handleSocialCallback({ url }).then((s) => setSession(s)).catch(() => {
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
client.handleCallback({ url }).then((s) => setSession(s)).catch(() => {
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
88
107
|
}
|
|
89
108
|
const sub = Linking.addEventListener("url", handleUrl);
|
|
90
109
|
Linking.getInitialURL().then((url) => {
|
|
@@ -111,6 +130,53 @@ function SmartHiveProvider({ children, ...config }) {
|
|
|
111
130
|
(input, init) => client.fetch(input, init),
|
|
112
131
|
[client]
|
|
113
132
|
);
|
|
133
|
+
function wrapHeadlessSignIn(fn) {
|
|
134
|
+
return async (params) => {
|
|
135
|
+
const result = await fn(params);
|
|
136
|
+
setSession({
|
|
137
|
+
accessToken: result.accessToken,
|
|
138
|
+
refreshToken: result.refreshToken,
|
|
139
|
+
expiresAt: result.expiresAt,
|
|
140
|
+
user: result.user
|
|
141
|
+
});
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const signIn = useMemo(() => {
|
|
146
|
+
const h = client.headless;
|
|
147
|
+
return {
|
|
148
|
+
email: wrapHeadlessSignIn(h.signIn.email.bind(h.signIn)),
|
|
149
|
+
phone: {
|
|
150
|
+
sendOtp: (p) => h.signIn.phone.sendOtp(p),
|
|
151
|
+
verify: wrapHeadlessSignIn(h.signIn.phone.verify.bind(h.signIn.phone))
|
|
152
|
+
},
|
|
153
|
+
emailOtp: {
|
|
154
|
+
send: (p) => h.signIn.emailOtp.send(p),
|
|
155
|
+
verify: wrapHeadlessSignIn(h.signIn.emailOtp.verify.bind(h.signIn.emailOtp))
|
|
156
|
+
},
|
|
157
|
+
magicLink: {
|
|
158
|
+
send: (p) => h.signIn.magicLink.send(p)
|
|
159
|
+
},
|
|
160
|
+
social: (provider, options) => client.loginSocial(provider, options)
|
|
161
|
+
};
|
|
162
|
+
}, [client]);
|
|
163
|
+
const signUp = useMemo(() => {
|
|
164
|
+
const h = client.headless;
|
|
165
|
+
return {
|
|
166
|
+
email: async (params) => {
|
|
167
|
+
const result = await h.signUp.email(params);
|
|
168
|
+
if (!result.requiresVerification) {
|
|
169
|
+
setSession({
|
|
170
|
+
accessToken: result.accessToken,
|
|
171
|
+
refreshToken: result.refreshToken,
|
|
172
|
+
expiresAt: result.expiresAt,
|
|
173
|
+
user: result.user
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}, [client]);
|
|
114
180
|
const value = useMemo(
|
|
115
181
|
() => ({
|
|
116
182
|
client,
|
|
@@ -121,9 +187,11 @@ function SmartHiveProvider({ children, ...config }) {
|
|
|
121
187
|
logout,
|
|
122
188
|
refreshSession,
|
|
123
189
|
getAuthorizationHeader,
|
|
124
|
-
authFetch
|
|
190
|
+
authFetch,
|
|
191
|
+
signIn,
|
|
192
|
+
signUp
|
|
125
193
|
}),
|
|
126
|
-
[client, session, isLoaded, login, logout, refreshSession, getAuthorizationHeader, authFetch]
|
|
194
|
+
[client, session, isLoaded, login, logout, refreshSession, getAuthorizationHeader, authFetch, signIn, signUp]
|
|
127
195
|
);
|
|
128
196
|
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
129
197
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { Linking } from \"react-native\";\nimport { AuthRequest } from \"expo-auth-session\";\nimport * as SecureStore from \"expo-secure-store\";\nimport {\n initAuth,\n type AuthSession,\n type AuthStorage,\n type SmartHiveAuthClient,\n type SmartHiveAuthConfig,\n} from \"@smarthivelabs-devs/auth-sdk\";\n\n// ── Secure storage adapter ────────────────────────────────────────────────────\n\nconst secureStorage: AuthStorage = {\n getItem: (key) => SecureStore.getItemAsync(key),\n setItem: (key, value) => SecureStore.setItemAsync(key, value),\n removeItem: (key) => SecureStore.deleteItemAsync(key),\n};\n\nconst storageKeys = {\n pkceVerifier: \"smarthive.auth.pkce_verifier\",\n pkceState: \"smarthive.auth.pkce_state\"\n} as const;\n\n// ── Config ────────────────────────────────────────────────────────────────────\n\nexport interface SmartHiveExpoConfig extends Omit<SmartHiveAuthConfig, \"storage\"> {\n /** Deep link redirect URI, e.g. \"myapp://auth/callback\" */\n redirectUri: string;\n}\n\n/**\n * Build a deep link redirect URI from your Expo app scheme.\n * @example buildRedirectUri(\"myapp\") → \"myapp://auth/callback\"\n */\nexport function buildRedirectUri(scheme: string, path = \"auth/callback\"): string {\n return `${scheme}://${path}`;\n}\n\nfunction normalizeBaseUrl(baseUrl: string) {\n return baseUrl.replace(/\\/$/, \"\");\n}\n\n// ── Auth client (Expo flavour) ─────────────────────────────────────────────────\n\n/**\n * Creates an auth client that uses SecureStore for token storage and\n * Linking.openURL for the OAuth redirect (instead of location.assign).\n */\nexport function initExpoAuth(config: SmartHiveExpoConfig): SmartHiveAuthClient {\n const base = initAuth({\n ...config,\n storage: secureStorage,\n temporaryStorage: secureStorage\n } as SmartHiveAuthConfig);\n\n return {\n ...base,\n async login(options) {\n const redirectUri = options?.redirectUri ?? config.redirectUri;\n const authBase = normalizeBaseUrl(config.authDomain ?? config.baseUrl);\n const request = new AuthRequest({\n clientId: config.publishableKey,\n redirectUri,\n responseType: \"code\",\n usePKCE: true,\n state: options?.state,\n extraParams: {\n project_id: config.projectId,\n publishable_key: config.publishableKey\n }\n });\n const url = await request.makeAuthUrlAsync({\n authorizationEndpoint: `${authBase}/api/auth/oauth2/authorize`\n });\n\n if (request.codeVerifier) {\n await secureStorage.setItem(storageKeys.pkceVerifier, request.codeVerifier);\n }\n await secureStorage.setItem(storageKeys.pkceState, request.state);\n await Linking.openURL(url);\n },\n };\n}\n\n// ── Context ────────────────────────────────────────────────────────────────────\n\ninterface AuthContextValue {\n client: SmartHiveAuthClient;\n session: AuthSession | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n login(options?: { redirectUri?: string }): Promise<void>;\n logout(): Promise<void>;\n refreshSession(): Promise<void>;\n getAuthorizationHeader(): Promise<Record<string, string>>;\n authFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ── Provider ───────────────────────────────────────────────────────────────────\n\nexport interface SmartHiveProviderProps extends SmartHiveExpoConfig {\n children: React.ReactNode;\n}\n\n/**\n * Wraps your Expo app and provides auth state to all child components.\n * Tokens are stored in SecureStore. OAuth is handled via deep links.\n *\n * @example\n * <SmartHiveProvider\n * projectId=\"proj_...\"\n * publishableKey=\"pk_...\"\n * baseUrl=\"https://authcore.smarthivelabs.dev/prod\"\n * redirectUri=\"myapp://auth/callback\"\n * >\n * <App />\n * </SmartHiveProvider>\n */\nexport function SmartHiveProvider({ children, ...config }: SmartHiveProviderProps) {\n const configRef = useRef(config);\n const client = useMemo(() => initExpoAuth(configRef.current), []);\n\n const [session, setSession] = useState<AuthSession | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Initial session load from SecureStore\n useEffect(() => {\n let cancelled = false;\n client\n .initialize()\n .then(() => client.getSession())\n .then((s) => { if (!cancelled) setSession(s); })\n .catch(() => {})\n .finally(() => { if (!cancelled) setIsLoaded(true); });\n return () => { cancelled = true; };\n }, [client]);\n\n // Deep link listener — catches the OAuth callback redirect back into the app\n useEffect(() => {\n function handleUrl({ url }: { url: string }) {\n client\n .handleCallback({ url })\n .then((s) => setSession(s))\n .catch(() => {});\n }\n\n const sub = Linking.addEventListener(\"url\", handleUrl);\n\n // Handle cold-start: app opened directly from the OAuth redirect URL\n Linking.getInitialURL().then((url) => {\n if (url) handleUrl({ url });\n });\n\n return () => sub.remove();\n }, [client]);\n\n const login = useCallback(\n (options?: { redirectUri?: string }) => client.login(options),\n [client]\n );\n\n const logout = useCallback(async () => {\n await client.logout();\n setSession(null);\n }, [client]);\n\n const refreshSession = useCallback(async () => {\n setSession(await client.refreshSession());\n }, [client]);\n\n const getAuthorizationHeader = useCallback(\n () => client.getAuthorizationHeader(),\n [client]\n );\n\n const authFetch = useCallback(\n (input: string | URL | Request, init?: RequestInit) => client.fetch(input, init),\n [client]\n );\n\n const value = useMemo<AuthContextValue>(\n () => ({\n client,\n session,\n isLoaded,\n isSignedIn: !!session,\n login,\n logout,\n refreshSession,\n getAuthorizationHeader,\n authFetch\n }),\n [client, session, isLoaded, login, logout, refreshSession, getAuthorizationHeader, authFetch]\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n// ── Hooks ──────────────────────────────────────────────────────────────────────\n\nfunction useAuthContext(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) throw new Error(\"useAuth must be used inside <SmartHiveProvider>.\");\n return ctx;\n}\n\nexport function useAuth() { return useAuthContext(); }\nexport function useSession() { return useAuthContext().session; }\nexport function useUser() { return useAuthContext().session?.user ?? null; }\nexport function useIsLoaded() { return useAuthContext().isLoaded; }\n\nexport function useIsSignedIn() {\n const { isLoaded, isSignedIn } = useAuthContext();\n return isLoaded ? isSignedIn : null;\n}\n\nexport function useAuthFetch() {\n return useAuthContext().authFetch;\n}\n\nexport function useAuthorizationHeader() {\n return useAuthContext().getAuthorizationHeader;\n}\n\n// ── Render helpers ─────────────────────────────────────────────────────────────\n\nexport function SignedIn({ children }: { children: React.ReactNode }) {\n const { isLoaded, isSignedIn } = useAuthContext();\n if (!isLoaded || !isSignedIn) return null;\n return <>{children}</>;\n}\n\nexport function SignedOut({ children }: { children: React.ReactNode }) {\n const { isLoaded, isSignedIn } = useAuthContext();\n if (!isLoaded || isSignedIn) return null;\n return <>{children}</>;\n}\n\nexport function AuthLoading({ children }: { children: React.ReactNode }) {\n const { isLoaded } = useAuthContext();\n return isLoaded ? null : <>{children}</>;\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,YAAY,iBAAiB;AAC7B;AAAA,EACE;AAAA,OAKK;AA6LE,SAkCA,UAlCA;AAzLT,IAAM,gBAA6B;AAAA,EACjC,SAAS,CAAC,QAAoB,yBAAa,GAAG;AAAA,EAC9C,SAAS,CAAC,KAAK,UAAsB,yBAAa,KAAK,KAAK;AAAA,EAC5D,YAAY,CAAC,QAAoB,4BAAgB,GAAG;AACtD;AAEA,IAAM,cAAc;AAAA,EAClB,cAAc;AAAA,EACd,WAAW;AACb;AAaO,SAAS,iBAAiB,QAAgB,OAAO,iBAAyB;AAC/E,SAAO,GAAG,MAAM,MAAM,IAAI;AAC5B;AAEA,SAAS,iBAAiB,SAAiB;AACzC,SAAO,QAAQ,QAAQ,OAAO,EAAE;AAClC;AAQO,SAAS,aAAa,QAAkD;AAC7E,QAAM,OAAO,SAAS;AAAA,IACpB,GAAG;AAAA,IACH,SAAS;AAAA,IACT,kBAAkB;AAAA,EACpB,CAAwB;AAExB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,MAAM,SAAS;AACnB,YAAM,cAAc,SAAS,eAAe,OAAO;AACnD,YAAM,WAAW,iBAAiB,OAAO,cAAc,OAAO,OAAO;AACrE,YAAM,UAAU,IAAI,YAAY;AAAA,QAC9B,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,cAAc;AAAA,QACd,SAAS;AAAA,QACT,OAAO,SAAS;AAAA,QAChB,aAAa;AAAA,UACX,YAAY,OAAO;AAAA,UACnB,iBAAiB,OAAO;AAAA,QAC1B;AAAA,MACF,CAAC;AACD,YAAM,MAAM,MAAM,QAAQ,iBAAiB;AAAA,QACzC,uBAAuB,GAAG,QAAQ;AAAA,MACpC,CAAC;AAED,UAAI,QAAQ,cAAc;AACxB,cAAM,cAAc,QAAQ,YAAY,cAAc,QAAQ,YAAY;AAAA,MAC5E;AACA,YAAM,cAAc,QAAQ,YAAY,WAAW,QAAQ,KAAK;AAChE,YAAM,QAAQ,QAAQ,GAAG;AAAA,IAC3B;AAAA,EACF;AACF;AAgBA,IAAM,cAAc,cAAuC,IAAI;AAsBxD,SAAS,kBAAkB,EAAE,UAAU,GAAG,OAAO,GAA2B;AACjF,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,SAAS,QAAQ,MAAM,aAAa,UAAU,OAAO,GAAG,CAAC,CAAC;AAEhE,QAAM,CAAC,SAAS,UAAU,IAAI,SAA6B,IAAI;AAC/D,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAG9C,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,WACG,WAAW,EACX,KAAK,MAAM,OAAO,WAAW,CAAC,EAC9B,KAAK,CAAC,MAAM;AAAE,UAAI,CAAC,UAAW,YAAW,CAAC;AAAA,IAAG,CAAC,EAC9C,MAAM,MAAM;AAAA,IAAC,CAAC,EACd,QAAQ,MAAM;AAAE,UAAI,CAAC,UAAW,aAAY,IAAI;AAAA,IAAG,CAAC;AACvD,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAM;AAAA,EACnC,GAAG,CAAC,MAAM,CAAC;AAGX,YAAU,MAAM;AACd,aAAS,UAAU,EAAE,IAAI,GAAoB;AAC3C,aACG,eAAe,EAAE,IAAI,CAAC,EACtB,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC,EACzB,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnB;AAEA,UAAM,MAAM,QAAQ,iBAAiB,OAAO,SAAS;AAGrD,YAAQ,cAAc,EAAE,KAAK,CAAC,QAAQ;AACpC,UAAI,IAAK,WAAU,EAAE,IAAI,CAAC;AAAA,IAC5B,CAAC;AAED,WAAO,MAAM,IAAI,OAAO;AAAA,EAC1B,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,QAAQ;AAAA,IACZ,CAAC,YAAuC,OAAO,MAAM,OAAO;AAAA,IAC5D,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,SAAS,YAAY,YAAY;AACrC,UAAM,OAAO,OAAO;AACpB,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,iBAAiB,YAAY,YAAY;AAC7C,eAAW,MAAM,OAAO,eAAe,CAAC;AAAA,EAC1C,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,yBAAyB;AAAA,IAC7B,MAAM,OAAO,uBAAuB;AAAA,IACpC,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,YAAY;AAAA,IAChB,CAAC,OAA+B,SAAuB,OAAO,MAAM,OAAO,IAAI;AAAA,IAC/E,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,CAAC,CAAC;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,SAAS,UAAU,OAAO,QAAQ,gBAAgB,wBAAwB,SAAS;AAAA,EAC9F;AAEA,SAAO,oBAAC,YAAY,UAAZ,EAAqB,OAAe,UAAS;AACvD;AAIA,SAAS,iBAAmC;AAC1C,QAAM,MAAM,WAAW,WAAW;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,kDAAkD;AAC5E,SAAO;AACT;AAEO,SAAS,UAAU;AAAE,SAAO,eAAe;AAAG;AAC9C,SAAS,aAAa;AAAE,SAAO,eAAe,EAAE;AAAS;AACzD,SAAS,UAAU;AAAE,SAAO,eAAe,EAAE,SAAS,QAAQ;AAAM;AACpE,SAAS,cAAc;AAAE,SAAO,eAAe,EAAE;AAAU;AAE3D,SAAS,gBAAgB;AAC9B,QAAM,EAAE,UAAU,WAAW,IAAI,eAAe;AAChD,SAAO,WAAW,aAAa;AACjC;AAEO,SAAS,eAAe;AAC7B,SAAO,eAAe,EAAE;AAC1B;AAEO,SAAS,yBAAyB;AACvC,SAAO,eAAe,EAAE;AAC1B;AAIO,SAAS,SAAS,EAAE,SAAS,GAAkC;AACpE,QAAM,EAAE,UAAU,WAAW,IAAI,eAAe;AAChD,MAAI,CAAC,YAAY,CAAC,WAAY,QAAO;AACrC,SAAO,gCAAG,UAAS;AACrB;AAEO,SAAS,UAAU,EAAE,SAAS,GAAkC;AACrE,QAAM,EAAE,UAAU,WAAW,IAAI,eAAe;AAChD,MAAI,CAAC,YAAY,WAAY,QAAO;AACpC,SAAO,gCAAG,UAAS;AACrB;AAEO,SAAS,YAAY,EAAE,SAAS,GAAkC;AACvE,QAAM,EAAE,SAAS,IAAI,eAAe;AACpC,SAAO,WAAW,OAAO,gCAAG,UAAS;AACvC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { Linking } from \"react-native\";\nimport { AuthRequest } from \"expo-auth-session\";\nimport * as SecureStore from \"expo-secure-store\";\nimport {\n initAuth,\n envFromPublishableKey,\n type AuthSession,\n type AuthStorage,\n type HeadlessClient,\n type HeadlessSignInResult,\n type HeadlessSignUpResult,\n type SmartHiveAuthClient,\n type SmartHiveAuthConfig,\n type SocialProvider,\n} from \"@smarthivelabs-devs/auth-sdk\";\n\n// ── Secure storage adapter ────────────────────────────────────────────────────\n\nconst secureStorage: AuthStorage = {\n getItem: (key) => SecureStore.getItemAsync(key),\n setItem: (key, value) => SecureStore.setItemAsync(key, value),\n removeItem: (key) => SecureStore.deleteItemAsync(key),\n};\n\nconst storageKeys = {\n pkceVerifier: \"smarthive.auth.pkce_verifier\",\n pkceState: \"smarthive.auth.pkce_state\"\n} as const;\n\n// ── Config ────────────────────────────────────────────────────────────────────\n\nexport interface SmartHiveExpoConfig extends Omit<SmartHiveAuthConfig, \"storage\"> {\n /** Deep link redirect URI, e.g. \"myapp://auth/callback\" */\n redirectUri: string;\n}\n\n/**\n * Build a deep link redirect URI from your Expo app scheme.\n * @example buildRedirectUri(\"myapp\") → \"myapp://auth/callback\"\n */\nexport function buildRedirectUri(scheme: string, path = \"auth/callback\"): string {\n return `${scheme}://${path}`;\n}\n\nfunction normalizeBaseUrl(baseUrl: string) {\n return baseUrl.replace(/\\/$/, \"\");\n}\n\n// ── Auth client (Expo flavour) ─────────────────────────────────────────────────\n\n/**\n * Creates an auth client that uses SecureStore for token storage and\n * Linking.openURL for the OAuth redirect (instead of location.assign).\n */\nexport function initExpoAuth(config: SmartHiveExpoConfig): SmartHiveAuthClient {\n const base = initAuth({\n ...config,\n storage: secureStorage,\n temporaryStorage: secureStorage\n } as SmartHiveAuthConfig);\n\n return {\n ...base,\n async login(options) {\n const redirectUri = options?.redirectUri ?? config.redirectUri;\n const authBase = normalizeBaseUrl(config.authDomain ?? config.baseUrl);\n const request = new AuthRequest({\n clientId: config.publishableKey,\n redirectUri,\n responseType: \"code\",\n usePKCE: true,\n state: options?.state,\n extraParams: {\n project_id: config.projectId,\n publishable_key: config.publishableKey\n }\n });\n const url = await request.makeAuthUrlAsync({\n authorizationEndpoint: `${authBase}/api/auth/oauth2/authorize`\n });\n\n if (request.codeVerifier) {\n await secureStorage.setItem(storageKeys.pkceVerifier, request.codeVerifier);\n }\n await secureStorage.setItem(storageKeys.pkceState, request.state);\n await Linking.openURL(url);\n },\n\n async loginSocial(provider, options) {\n const redirectUri = options?.redirectUri ?? config.redirectUri;\n const authBase = normalizeBaseUrl(config.authDomain ?? config.baseUrl);\n const env = envFromPublishableKey(config.publishableKey);\n const url = new URL(`${authBase}/${env}/api/auth/social/${provider}`);\n url.searchParams.set(\"project_id\", config.projectId);\n url.searchParams.set(\"redirect_uri\", redirectUri);\n await Linking.openURL(url.toString());\n },\n };\n}\n\n// ── Context ────────────────────────────────────────────────────────────────────\n\ninterface AuthContextValue {\n client: SmartHiveAuthClient;\n session: AuthSession | null;\n isLoaded: boolean;\n isSignedIn: boolean;\n /** OAuth 2.0 + PKCE browser redirect sign-in (unchanged). */\n login(options?: { redirectUri?: string }): Promise<void>;\n logout(): Promise<void>;\n refreshSession(): Promise<void>;\n getAuthorizationHeader(): Promise<Record<string, string>>;\n authFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;\n /**\n * Headless (no browser) sign-in methods for custom login screens.\n * Tokens are stored in SecureStore automatically on success.\n */\n signIn: {\n email(params: { email: string; password: string }): Promise<HeadlessSignInResult>;\n phone: {\n sendOtp(params: { phoneNumber: string }): Promise<void>;\n verify(params: { phoneNumber: string; code: string }): Promise<HeadlessSignInResult>;\n };\n emailOtp: {\n send(params: { email: string }): Promise<void>;\n verify(params: { email: string; code: string }): Promise<HeadlessSignInResult>;\n };\n magicLink: {\n send(params: { email: string; callbackURL?: string }): Promise<void>;\n };\n /** Initiate a social OAuth flow — opens the provider's consent screen via Linking. */\n social(provider: SocialProvider, options?: { redirectUri?: string }): Promise<void>;\n };\n signUp: {\n email(params: { email: string; password: string; name?: string }): Promise<HeadlessSignUpResult>;\n };\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ── Provider ───────────────────────────────────────────────────────────────────\n\nexport interface SmartHiveProviderProps extends SmartHiveExpoConfig {\n children: React.ReactNode;\n}\n\n/**\n * Wraps your Expo app and provides auth state to all child components.\n * Tokens are stored in SecureStore. Supports both:\n * - OAuth 2.0 + PKCE browser redirect via `login()`\n * - Headless direct sign-in via `signIn.*` (no browser, custom login screens)\n */\nexport function SmartHiveProvider({ children, ...config }: SmartHiveProviderProps) {\n const configRef = useRef(config);\n const client = useMemo(() => initExpoAuth(configRef.current), []);\n\n const [session, setSession] = useState<AuthSession | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n\n // Initial session load from SecureStore\n useEffect(() => {\n let cancelled = false;\n client\n .initialize()\n .then(() => client.getSession())\n .then((s) => { if (!cancelled) setSession(s); })\n .catch(() => {})\n .finally(() => { if (!cancelled) setIsLoaded(true); });\n return () => { cancelled = true; };\n }, [client]);\n\n // Deep link listener — catches both PKCE and social OAuth callbacks\n useEffect(() => {\n function handleUrl({ url }: { url: string }) {\n try {\n const parsed = new URL(url);\n if (parsed.searchParams.has(\"access_token\") || parsed.searchParams.has(\"error\")) {\n // Social auth callback — tokens already in URL params\n client\n .handleSocialCallback({ url })\n .then((s) => setSession(s))\n .catch(() => {});\n } else {\n // PKCE OAuth callback — exchange code for tokens\n client\n .handleCallback({ url })\n .then((s) => setSession(s))\n .catch(() => {});\n }\n } catch {\n // Unparseable URL — ignore\n }\n }\n\n const sub = Linking.addEventListener(\"url\", handleUrl);\n\n // Handle cold-start: app opened directly from the OAuth redirect URL\n Linking.getInitialURL().then((url) => {\n if (url) handleUrl({ url });\n });\n\n return () => sub.remove();\n }, [client]);\n\n const login = useCallback(\n (options?: { redirectUri?: string }) => client.login(options),\n [client]\n );\n\n const logout = useCallback(async () => {\n await client.logout();\n setSession(null);\n }, [client]);\n\n const refreshSession = useCallback(async () => {\n setSession(await client.refreshSession());\n }, [client]);\n\n const getAuthorizationHeader = useCallback(\n () => client.getAuthorizationHeader(),\n [client]\n );\n\n const authFetch = useCallback(\n (input: string | URL | Request, init?: RequestInit) => client.fetch(input, init),\n [client]\n );\n\n // ── Headless sign-in wrappers — save session + update state ──────────────────\n\n function wrapHeadlessSignIn(\n fn: (p: never) => Promise<HeadlessSignInResult>\n ) {\n return async (params: never) => {\n const result = await fn(params);\n setSession({\n accessToken: result.accessToken,\n refreshToken: result.refreshToken,\n expiresAt: result.expiresAt,\n user: result.user,\n });\n return result;\n };\n }\n\n const signIn = useMemo<AuthContextValue[\"signIn\"]>(() => {\n const h: HeadlessClient = client.headless;\n return {\n email: wrapHeadlessSignIn(h.signIn.email.bind(h.signIn) as never),\n phone: {\n sendOtp: (p) => h.signIn.phone.sendOtp(p),\n verify: wrapHeadlessSignIn(h.signIn.phone.verify.bind(h.signIn.phone) as never),\n },\n emailOtp: {\n send: (p) => h.signIn.emailOtp.send(p),\n verify: wrapHeadlessSignIn(h.signIn.emailOtp.verify.bind(h.signIn.emailOtp) as never),\n },\n magicLink: {\n send: (p) => h.signIn.magicLink.send(p),\n },\n social: (provider, options) => client.loginSocial(provider, options),\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client]);\n\n const signUp = useMemo<AuthContextValue[\"signUp\"]>(() => {\n const h: HeadlessClient = client.headless;\n return {\n email: async (params) => {\n const result = await h.signUp.email(params);\n if (!result.requiresVerification) {\n setSession({\n accessToken: result.accessToken,\n refreshToken: result.refreshToken,\n expiresAt: result.expiresAt,\n user: result.user,\n });\n }\n return result;\n },\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client]);\n\n const value = useMemo<AuthContextValue>(\n () => ({\n client,\n session,\n isLoaded,\n isSignedIn: !!session,\n login,\n logout,\n refreshSession,\n getAuthorizationHeader,\n authFetch,\n signIn,\n signUp,\n }),\n [client, session, isLoaded, login, logout, refreshSession, getAuthorizationHeader, authFetch, signIn, signUp]\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n// ── Hooks ──────────────────────────────────────────────────────────────────────\n\nfunction useAuthContext(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) throw new Error(\"useAuth must be used inside <SmartHiveProvider>.\");\n return ctx;\n}\n\nexport function useAuth() { return useAuthContext(); }\nexport function useSession() { return useAuthContext().session; }\nexport function useUser() { return useAuthContext().session?.user ?? null; }\nexport function useIsLoaded() { return useAuthContext().isLoaded; }\n\nexport function useIsSignedIn() {\n const { isLoaded, isSignedIn } = useAuthContext();\n return isLoaded ? isSignedIn : null;\n}\n\nexport function useAuthFetch() {\n return useAuthContext().authFetch;\n}\n\nexport function useAuthorizationHeader() {\n return useAuthContext().getAuthorizationHeader;\n}\n\n// ── Render helpers ─────────────────────────────────────────────────────────────\n\nexport function SignedIn({ children }: { children: React.ReactNode }) {\n const { isLoaded, isSignedIn } = useAuthContext();\n if (!isLoaded || !isSignedIn) return null;\n return <>{children}</>;\n}\n\nexport function SignedOut({ children }: { children: React.ReactNode }) {\n const { isLoaded, isSignedIn } = useAuthContext();\n if (!isLoaded || isSignedIn) return null;\n return <>{children}</>;\n}\n\nexport function AuthLoading({ children }: { children: React.ReactNode }) {\n const { isLoaded } = useAuthContext();\n return isLoaded ? null : <>{children}</>;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smarthivelabs-devs/auth-expo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "SmartHive Auth provider, hooks, and SecureStore integration for React Native / Expo",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"LICENSE"
|
|
26
26
|
],
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@smarthivelabs-devs/auth-sdk": "^1.
|
|
28
|
+
"@smarthivelabs-devs/auth-sdk": "^1.2.0",
|
|
29
29
|
"expo-auth-session": ">=5",
|
|
30
30
|
"expo-secure-store": ">=12",
|
|
31
31
|
"react": ">=18",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@types/react-native": "^0.73.0",
|
|
37
37
|
"tsup": "^8.3.0",
|
|
38
38
|
"typescript": "^5.7.3",
|
|
39
|
-
"@smarthivelabs-devs/auth-sdk": "^1.
|
|
39
|
+
"@smarthivelabs-devs/auth-sdk": "^1.2.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsup",
|