authkit-react-native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +131 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +184 -0
- package/package.json +85 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joshua Baker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# AuthKit for React Native
|
|
2
|
+
|
|
3
|
+
WorkOS AuthKit integration for React Native. Handles the full OAuth 2.0 PKCE flow, token storage, automatic session restoration, and token refresh — built on Zustand and Expo modules.
|
|
4
|
+
|
|
5
|
+
For server-side auth proxy examples (Hono, Next.js), see the [`examples/`](examples/) directory.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx expo install authkit-react-native zustand expo-auth-session expo-secure-store expo-web-browser @react-native-async-storage/async-storage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Client Usage
|
|
14
|
+
|
|
15
|
+
### Create the hook
|
|
16
|
+
|
|
17
|
+
Create a `useAuth` hook that your app imports everywhere. The store is created at module scope so the session begins restoring immediately on app launch.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// hooks/useAuth.ts
|
|
21
|
+
import { createAuthStore } from "authkit-react-native";
|
|
22
|
+
import { useStore } from "zustand";
|
|
23
|
+
|
|
24
|
+
const authStore = createAuthStore({
|
|
25
|
+
authorizationEndpoint: `${process.env.EXPO_PUBLIC_API_URL}/v1/auth/authorize`,
|
|
26
|
+
tokenEndpoint: `${process.env.EXPO_PUBLIC_API_URL}/v1/auth/token`,
|
|
27
|
+
revocationEndpoint: `${process.env.EXPO_PUBLIC_API_URL}/v1/auth/revoke`,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export function useAuth() {
|
|
31
|
+
return useStore(authStore);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Protected routes
|
|
36
|
+
|
|
37
|
+
Because the store lives outside React, there's no provider to wrap your app in. Just call `useAuth()` in a layout to gate routes while the session is loading:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// app/(auth)/_layout.tsx
|
|
41
|
+
import { useAuth } from "@/hooks/useAuth";
|
|
42
|
+
import { Redirect, Stack } from "expo-router";
|
|
43
|
+
|
|
44
|
+
export default function AuthLayout() {
|
|
45
|
+
const { user, isLoading } = useAuth();
|
|
46
|
+
|
|
47
|
+
if (isLoading) return <Loading />;
|
|
48
|
+
if (!user) return <Redirect href="/sign-in" />;
|
|
49
|
+
|
|
50
|
+
return <Stack />;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Sign in
|
|
55
|
+
|
|
56
|
+
Call `signIn()` to open the WorkOS AuthKit sign-in page. Returns `true` on success, `false` if the user cancelled.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useAuth } from "@/hooks/useAuth";
|
|
60
|
+
|
|
61
|
+
function SignInScreen() {
|
|
62
|
+
const { signIn } = useAuth();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<Button title="Sign in" onPress={() => signIn()} />
|
|
67
|
+
<Button
|
|
68
|
+
title="Sign up"
|
|
69
|
+
onPress={() => signIn({ screenHint: "sign-up" })}
|
|
70
|
+
/>
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Sign out
|
|
77
|
+
|
|
78
|
+
`signOut()` revokes the token and clears storage. It does not show any confirmation UI — add that in your app:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { useAuth } from "@/hooks/useAuth";
|
|
82
|
+
import { Alert } from "react-native";
|
|
83
|
+
|
|
84
|
+
function SignOutButton() {
|
|
85
|
+
const { user, signOut } = useAuth();
|
|
86
|
+
|
|
87
|
+
const handleSignOut = () => {
|
|
88
|
+
Alert.alert(
|
|
89
|
+
user?.email
|
|
90
|
+
? `Are you sure you want to sign out as ${user.email}?`
|
|
91
|
+
: "Are you sure you want to sign out?",
|
|
92
|
+
undefined,
|
|
93
|
+
[
|
|
94
|
+
{ text: "Cancel", style: "cancel" },
|
|
95
|
+
{ text: "Sign out", style: "destructive", onPress: () => signOut() },
|
|
96
|
+
],
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return <Button title="Sign out" onPress={handleSignOut} />;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Bare React Native (without Expo)
|
|
105
|
+
|
|
106
|
+
This package is built around Expo modules but works in any React Native app. Expo packages can be used in bare React Native projects by adding the Expo modules runtime:
|
|
107
|
+
|
|
108
|
+
1. Install the `expo` package:
|
|
109
|
+
```bash
|
|
110
|
+
npm install expo
|
|
111
|
+
npx install-expo-modules
|
|
112
|
+
```
|
|
113
|
+
2. Install the peer dependencies using `npx expo install` (handles native linking):
|
|
114
|
+
```bash
|
|
115
|
+
npx expo install expo-auth-session expo-secure-store expo-web-browser @react-native-async-storage/async-storage zustand
|
|
116
|
+
```
|
|
117
|
+
3. Ensure your app's URL scheme is configured for the OAuth redirect. In `app.json` or your native project's `Info.plist` / `AndroidManifest.xml`, register a deep link scheme (e.g., `myapp://`).
|
|
118
|
+
|
|
119
|
+
After that, usage is identical to the examples above.
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
| Option | Required | Default | Description |
|
|
124
|
+
| ----------------------- | -------- | ------------------- | ------------------------------------------------------------------------- |
|
|
125
|
+
| `authorizationEndpoint` | \* | WorkOS default | OAuth authorize URL. Required unless `clientId` is provided. |
|
|
126
|
+
| `clientId` | \* | — | WorkOS client ID. Required when using the default authorization endpoint. |
|
|
127
|
+
| `tokenEndpoint` | Yes | — | Token exchange URL. |
|
|
128
|
+
| `revocationEndpoint` | Yes | — | Token revocation URL. |
|
|
129
|
+
| `redirectUri` | No | `makeRedirectUri()` | OAuth redirect URI. |
|
|
130
|
+
| `storageKeyPrefix` | No | `"workos"` | Prefix for SecureStore/AsyncStorage keys. |
|
|
131
|
+
| `devMode` | No | `false` | Logs errors to the console when enabled. |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as zustand from 'zustand';
|
|
2
|
+
|
|
3
|
+
type ClientIdOrAuthorizationEndpoint = {
|
|
4
|
+
clientId?: string;
|
|
5
|
+
authorizationEndpoint: string;
|
|
6
|
+
} | {
|
|
7
|
+
clientId: string;
|
|
8
|
+
authorizationEndpoint?: string;
|
|
9
|
+
};
|
|
10
|
+
type AuthConfig = ClientIdOrAuthorizationEndpoint & {
|
|
11
|
+
tokenEndpoint: string;
|
|
12
|
+
revocationEndpoint: string;
|
|
13
|
+
redirectUri?: string;
|
|
14
|
+
storageKeyPrefix?: string;
|
|
15
|
+
devMode?: boolean;
|
|
16
|
+
};
|
|
17
|
+
type User = {
|
|
18
|
+
id: string;
|
|
19
|
+
email: string;
|
|
20
|
+
emailVerified: boolean;
|
|
21
|
+
profilePictureUrl: string | null;
|
|
22
|
+
firstName: string | null;
|
|
23
|
+
lastName: string | null;
|
|
24
|
+
lastSignInAt: string | null;
|
|
25
|
+
locale: string | null;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
externalId: string | null;
|
|
29
|
+
metadata: Record<string, string>;
|
|
30
|
+
};
|
|
31
|
+
type Impersonator = {
|
|
32
|
+
email: string;
|
|
33
|
+
reason: string | null;
|
|
34
|
+
};
|
|
35
|
+
type AuthenticationMethod = "SSO" | "Password" | "Passkey" | "AppleOAuth" | "BitbucketOAuth" | "GitHubOAuth" | "GitLabOAuth" | "GoogleOAuth" | "LinkedInOAuth" | "MicrosoftOAuth" | "SalesforceOAuth" | "VercelOAuth" | "MagicAuth" | "CrossAppAuth" | "Impersonation";
|
|
36
|
+
type AuthSessionInfo = {
|
|
37
|
+
user: User;
|
|
38
|
+
organizationId?: string;
|
|
39
|
+
impersonator?: Impersonator;
|
|
40
|
+
authenticationMethod?: AuthenticationMethod;
|
|
41
|
+
};
|
|
42
|
+
type AuthState = {
|
|
43
|
+
isLoading: boolean;
|
|
44
|
+
} & Partial<AuthSessionInfo> & {
|
|
45
|
+
signIn: (options?: {
|
|
46
|
+
screenHint?: "sign-up" | "sign-in";
|
|
47
|
+
}) => Promise<boolean>;
|
|
48
|
+
signOut: () => Promise<void>;
|
|
49
|
+
getAccessToken: (options?: {
|
|
50
|
+
forceRefresh?: boolean;
|
|
51
|
+
}) => Promise<string | null>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
declare function createAuthStore(config: AuthConfig): zustand.StoreApi<AuthState>;
|
|
55
|
+
|
|
56
|
+
export { type AuthConfig, type AuthSessionInfo, type AuthState, type AuthenticationMethod, type Impersonator, type User, createAuthStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// src/store.ts
|
|
2
|
+
import {
|
|
3
|
+
AccessTokenRequest,
|
|
4
|
+
AuthRequest,
|
|
5
|
+
makeRedirectUri,
|
|
6
|
+
RefreshTokenRequest,
|
|
7
|
+
RevokeTokenRequest,
|
|
8
|
+
TokenResponse
|
|
9
|
+
} from "expo-auth-session";
|
|
10
|
+
import * as WebBrowser from "expo-web-browser";
|
|
11
|
+
import { createStore } from "zustand";
|
|
12
|
+
|
|
13
|
+
// src/storage.ts
|
|
14
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
15
|
+
import * as SecureStore from "expo-secure-store";
|
|
16
|
+
function createStorage(prefix) {
|
|
17
|
+
const tokensKey = `${prefix}.tokens`;
|
|
18
|
+
const metaKey = `${prefix}.meta`;
|
|
19
|
+
return {
|
|
20
|
+
async readTokens() {
|
|
21
|
+
return SecureStore.getItemAsync(tokensKey);
|
|
22
|
+
},
|
|
23
|
+
async writeTokens(value) {
|
|
24
|
+
await SecureStore.setItemAsync(tokensKey, value);
|
|
25
|
+
},
|
|
26
|
+
async readMeta() {
|
|
27
|
+
return AsyncStorage.getItem(metaKey);
|
|
28
|
+
},
|
|
29
|
+
async writeMeta(value) {
|
|
30
|
+
await AsyncStorage.setItem(metaKey, value);
|
|
31
|
+
},
|
|
32
|
+
async clear() {
|
|
33
|
+
await Promise.all([
|
|
34
|
+
SecureStore.deleteItemAsync(tokensKey),
|
|
35
|
+
AsyncStorage.removeItem(metaKey)
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/store.ts
|
|
42
|
+
var defaultAuthorizationEndpoint = "https://api.workos.com/user_management/authorize";
|
|
43
|
+
WebBrowser.maybeCompleteAuthSession();
|
|
44
|
+
var emptySession = {
|
|
45
|
+
user: void 0,
|
|
46
|
+
organizationId: void 0,
|
|
47
|
+
impersonator: void 0,
|
|
48
|
+
authenticationMethod: void 0
|
|
49
|
+
};
|
|
50
|
+
function assertAuthSessionInfo(response) {
|
|
51
|
+
if (!response || typeof response !== "object" || !("user" in response)) {
|
|
52
|
+
throw new Error("Invalid authentication response");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function createAuthStore(config) {
|
|
56
|
+
const {
|
|
57
|
+
clientId = "",
|
|
58
|
+
redirectUri = makeRedirectUri(),
|
|
59
|
+
authorizationEndpoint = defaultAuthorizationEndpoint,
|
|
60
|
+
tokenEndpoint,
|
|
61
|
+
revocationEndpoint,
|
|
62
|
+
storageKeyPrefix = "workos",
|
|
63
|
+
devMode = false
|
|
64
|
+
} = config;
|
|
65
|
+
if (authorizationEndpoint === defaultAuthorizationEndpoint && !clientId) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"clientId is required when using default authorizationEndpoint"
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const storage = createStorage(storageKeyPrefix);
|
|
71
|
+
WebBrowser.warmUpAsync();
|
|
72
|
+
async function readTokenResponse() {
|
|
73
|
+
const stored = await storage.readTokens();
|
|
74
|
+
if (!stored) return null;
|
|
75
|
+
const requestConfig = JSON.parse(stored);
|
|
76
|
+
return new TokenResponse(requestConfig);
|
|
77
|
+
}
|
|
78
|
+
async function storeTokenResponse(tokenResponse) {
|
|
79
|
+
assertAuthSessionInfo(tokenResponse.rawResponse);
|
|
80
|
+
const requestConfig = tokenResponse.getRequestConfig();
|
|
81
|
+
const { user, organizationId, impersonator, authenticationMethod } = tokenResponse.rawResponse;
|
|
82
|
+
const meta = { user, organizationId, impersonator, authenticationMethod };
|
|
83
|
+
await Promise.all([
|
|
84
|
+
storage.writeTokens(JSON.stringify(requestConfig)),
|
|
85
|
+
storage.writeMeta(JSON.stringify(meta))
|
|
86
|
+
]);
|
|
87
|
+
return meta;
|
|
88
|
+
}
|
|
89
|
+
const store = createStore()((set) => ({
|
|
90
|
+
isLoading: true,
|
|
91
|
+
signIn: async ({ screenHint = "sign-in" } = {}) => {
|
|
92
|
+
const authSessionRequest = new AuthRequest({
|
|
93
|
+
clientId,
|
|
94
|
+
redirectUri,
|
|
95
|
+
extraParams: { provider: "authkit", screen_hint: screenHint }
|
|
96
|
+
});
|
|
97
|
+
const authSessionResult = await authSessionRequest.promptAsync({
|
|
98
|
+
authorizationEndpoint
|
|
99
|
+
});
|
|
100
|
+
if (authSessionResult.type === "error") {
|
|
101
|
+
throw new Error(
|
|
102
|
+
authSessionResult.error?.description ?? "Unknown error"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (authSessionResult.type !== "success") {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (authSessionRequest.state !== authSessionResult.params.state) {
|
|
109
|
+
throw new Error("State mismatch");
|
|
110
|
+
}
|
|
111
|
+
if (!authSessionRequest.codeVerifier) {
|
|
112
|
+
throw new Error("Code verifier missing");
|
|
113
|
+
}
|
|
114
|
+
const tokenRequest = new AccessTokenRequest({
|
|
115
|
+
code: authSessionResult.params.code,
|
|
116
|
+
clientId,
|
|
117
|
+
redirectUri,
|
|
118
|
+
extraParams: {
|
|
119
|
+
code_verifier: authSessionRequest.codeVerifier
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const tokenResponse = await tokenRequest.performAsync({ tokenEndpoint });
|
|
123
|
+
const meta = await storeTokenResponse(tokenResponse);
|
|
124
|
+
set(meta);
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
signOut: async () => {
|
|
128
|
+
const tokenResponse = await readTokenResponse();
|
|
129
|
+
if (tokenResponse?.accessToken) {
|
|
130
|
+
const revokeTokenRequest = new RevokeTokenRequest({
|
|
131
|
+
clientId,
|
|
132
|
+
token: tokenResponse.accessToken
|
|
133
|
+
});
|
|
134
|
+
await revokeTokenRequest.performAsync({ revocationEndpoint });
|
|
135
|
+
}
|
|
136
|
+
await storage.clear();
|
|
137
|
+
set(emptySession);
|
|
138
|
+
},
|
|
139
|
+
getAccessToken: async (options) => {
|
|
140
|
+
const forceRefresh = options?.forceRefresh ?? false;
|
|
141
|
+
try {
|
|
142
|
+
let tokenResponse = await readTokenResponse();
|
|
143
|
+
if (!tokenResponse) {
|
|
144
|
+
await storage.clear();
|
|
145
|
+
set(emptySession);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
if (forceRefresh || tokenResponse.shouldRefresh()) {
|
|
149
|
+
const refreshTokenRequest = new RefreshTokenRequest({
|
|
150
|
+
refreshToken: tokenResponse.refreshToken,
|
|
151
|
+
clientId
|
|
152
|
+
});
|
|
153
|
+
tokenResponse = await refreshTokenRequest.performAsync({
|
|
154
|
+
tokenEndpoint
|
|
155
|
+
});
|
|
156
|
+
const meta = await storeTokenResponse(tokenResponse);
|
|
157
|
+
set(meta);
|
|
158
|
+
}
|
|
159
|
+
return tokenResponse.accessToken;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (devMode) console.error(error);
|
|
162
|
+
await storage.clear();
|
|
163
|
+
set(emptySession);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
(async () => {
|
|
169
|
+
try {
|
|
170
|
+
const storedMeta = await storage.readMeta();
|
|
171
|
+
if (storedMeta !== null) {
|
|
172
|
+
const meta = JSON.parse(storedMeta);
|
|
173
|
+
store.setState(meta);
|
|
174
|
+
}
|
|
175
|
+
await store.getState().getAccessToken({ forceRefresh: true });
|
|
176
|
+
} finally {
|
|
177
|
+
store.setState({ isLoading: false });
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
return store;
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
createAuthStore
|
|
184
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "authkit-react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "WorkOS AuthKit integration for React Native — OAuth 2.0 PKCE auth with token storage, session restoration, and automatic refresh",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"provenance": true
|
|
23
|
+
},
|
|
24
|
+
"simple-git-hooks": {
|
|
25
|
+
"pre-commit": "pnpm lint-staged"
|
|
26
|
+
},
|
|
27
|
+
"lint-staged": {
|
|
28
|
+
"*": "prettier --write --ignore-unknown"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/joshuabaker/authkit-react-native.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/joshuabaker/authkit-react-native#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/joshuabaker/authkit-react-native/issues"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"workos",
|
|
40
|
+
"authkit",
|
|
41
|
+
"react-native",
|
|
42
|
+
"expo",
|
|
43
|
+
"oauth",
|
|
44
|
+
"pkce",
|
|
45
|
+
"authentication",
|
|
46
|
+
"zustand"
|
|
47
|
+
],
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@react-native-async-storage/async-storage": ">=2",
|
|
50
|
+
"expo-auth-session": ">=5",
|
|
51
|
+
"expo-secure-store": ">=13",
|
|
52
|
+
"expo-web-browser": ">=13",
|
|
53
|
+
"react": ">=18",
|
|
54
|
+
"zustand": ">=4"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@changesets/changelog-github": "^0.6.0",
|
|
58
|
+
"@changesets/cli": "^2.30.0",
|
|
59
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
60
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
61
|
+
"expo-auth-session": "^55.0.7",
|
|
62
|
+
"expo-secure-store": "^55.0.8",
|
|
63
|
+
"expo-web-browser": "^55.0.9",
|
|
64
|
+
"lint-staged": "^16.1.0",
|
|
65
|
+
"prettier": "^3.8.1",
|
|
66
|
+
"react": "^19.2.0",
|
|
67
|
+
"simple-git-hooks": "^2.13.0",
|
|
68
|
+
"tsup": "^8.4.0",
|
|
69
|
+
"typescript": "^5.0.0",
|
|
70
|
+
"vitest": "^4.1.0",
|
|
71
|
+
"zustand": "^5.0.12"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsup",
|
|
75
|
+
"format": "prettier --write .",
|
|
76
|
+
"format:check": "prettier --check .",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:watch": "vitest",
|
|
79
|
+
"test:coverage": "vitest run --coverage",
|
|
80
|
+
"type-check": "tsc --noEmit",
|
|
81
|
+
"changeset": "changeset",
|
|
82
|
+
"version": "changeset version",
|
|
83
|
+
"release": "changeset publish"
|
|
84
|
+
}
|
|
85
|
+
}
|