@phantom/react-native-sdk 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/README.md +440 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +479 -0
- package/dist/index.mjs +434 -0
- package/package.json +85 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
// src/PhantomProvider.tsx
|
|
2
|
+
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from "react";
|
|
3
|
+
import { EmbeddedProvider } from "@phantom/embedded-provider-core";
|
|
4
|
+
|
|
5
|
+
// src/providers/embedded/storage.ts
|
|
6
|
+
import * as SecureStore from "expo-secure-store";
|
|
7
|
+
var ExpoSecureStorage = class {
|
|
8
|
+
constructor(requireAuth = false) {
|
|
9
|
+
this.sessionKey = "phantom_session";
|
|
10
|
+
this.requireAuth = requireAuth;
|
|
11
|
+
}
|
|
12
|
+
async saveSession(session) {
|
|
13
|
+
try {
|
|
14
|
+
await SecureStore.setItemAsync(this.sessionKey, JSON.stringify(session), {
|
|
15
|
+
requireAuthentication: this.requireAuth,
|
|
16
|
+
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY
|
|
17
|
+
});
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("[ExpoSecureStorage] Failed to save session", { error: error.message });
|
|
20
|
+
throw new Error(`Failed to save session: ${error.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async getSession() {
|
|
24
|
+
try {
|
|
25
|
+
const sessionData = await SecureStore.getItemAsync(this.sessionKey, {
|
|
26
|
+
requireAuthentication: this.requireAuth
|
|
27
|
+
});
|
|
28
|
+
if (!sessionData) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return JSON.parse(sessionData);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("[ExpoSecureStorage] Failed to load session", { error: error.message });
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async clearSession() {
|
|
38
|
+
try {
|
|
39
|
+
await SecureStore.deleteItemAsync(this.sessionKey);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("[ExpoSecureStorage] Failed to clear session", { error: error.message });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async isAvailable() {
|
|
45
|
+
return await SecureStore.isAvailableAsync();
|
|
46
|
+
}
|
|
47
|
+
// Method to update authentication requirement
|
|
48
|
+
setRequireAuth(_requireAuth) {
|
|
49
|
+
console.warn("[ExpoSecureStorage] Cannot change requireAuth after initialization");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/providers/embedded/auth.ts
|
|
54
|
+
import * as WebBrowser from "expo-web-browser";
|
|
55
|
+
var ExpoAuthProvider = class {
|
|
56
|
+
async authenticate(options) {
|
|
57
|
+
if ("jwtToken" in options) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { authUrl, redirectUrl } = options;
|
|
61
|
+
if (!authUrl || !redirectUrl) {
|
|
62
|
+
throw new Error("authUrl and redirectUrl are required for web browser authentication");
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
console.log("[ExpoAuthProvider] Starting authentication", {
|
|
66
|
+
authUrl: authUrl.substring(0, 50) + "...",
|
|
67
|
+
redirectUrl
|
|
68
|
+
});
|
|
69
|
+
await WebBrowser.warmUpAsync();
|
|
70
|
+
const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl, {
|
|
71
|
+
// Use system browser on iOS for ASWebAuthenticationSession
|
|
72
|
+
preferEphemeralSession: false
|
|
73
|
+
});
|
|
74
|
+
console.log("[ExpoAuthProvider] Authentication result", {
|
|
75
|
+
type: result.type,
|
|
76
|
+
url: result.type === "success" && result.url ? result.url.substring(0, 100) + "..." : void 0
|
|
77
|
+
});
|
|
78
|
+
if (result.type === "success" && result.url) {
|
|
79
|
+
const url = new URL(result.url);
|
|
80
|
+
const walletId = url.searchParams.get("walletId");
|
|
81
|
+
const provider = url.searchParams.get("provider");
|
|
82
|
+
if (!walletId) {
|
|
83
|
+
throw new Error("Authentication failed: no walletId in redirect URL");
|
|
84
|
+
}
|
|
85
|
+
const userInfo = {};
|
|
86
|
+
url.searchParams.forEach((value, key) => {
|
|
87
|
+
userInfo[key] = value;
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
walletId,
|
|
91
|
+
provider: provider || void 0,
|
|
92
|
+
userInfo
|
|
93
|
+
};
|
|
94
|
+
} else if (result.type === "cancel") {
|
|
95
|
+
throw new Error("User cancelled authentication");
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error("Authentication failed");
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("[ExpoAuthProvider] Authentication error", error);
|
|
101
|
+
throw error;
|
|
102
|
+
} finally {
|
|
103
|
+
await WebBrowser.coolDownAsync();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
isAvailable() {
|
|
107
|
+
return Promise.resolve(true);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/providers/embedded/url-params.ts
|
|
112
|
+
import { Linking } from "react-native";
|
|
113
|
+
var ExpoURLParamsAccessor = class {
|
|
114
|
+
constructor() {
|
|
115
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
116
|
+
this.subscription = null;
|
|
117
|
+
this.currentParams = {};
|
|
118
|
+
}
|
|
119
|
+
getParam(key) {
|
|
120
|
+
return this.currentParams[key] || null;
|
|
121
|
+
}
|
|
122
|
+
async getInitialParams() {
|
|
123
|
+
try {
|
|
124
|
+
const url = await Linking.getInitialURL();
|
|
125
|
+
if (!url) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const params = this.parseURLParams(url);
|
|
129
|
+
this.currentParams = params;
|
|
130
|
+
return params;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error("[ExpoURLParamsAccessor] Failed to get initial URL", error);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
startListening() {
|
|
137
|
+
if (this.subscription) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.subscription = Linking.addEventListener("url", ({ url }) => {
|
|
141
|
+
const params = this.parseURLParams(url);
|
|
142
|
+
if (params && Object.keys(params).length > 0) {
|
|
143
|
+
this.currentParams = { ...this.currentParams, ...params };
|
|
144
|
+
this.listeners.forEach((listener) => listener(params));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
stopListening() {
|
|
149
|
+
if (this.subscription) {
|
|
150
|
+
this.subscription.remove();
|
|
151
|
+
this.subscription = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
addListener(callback) {
|
|
155
|
+
this.listeners.add(callback);
|
|
156
|
+
return () => {
|
|
157
|
+
this.listeners.delete(callback);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
parseURLParams(url) {
|
|
161
|
+
try {
|
|
162
|
+
const parsed = new URL(url);
|
|
163
|
+
const params = {};
|
|
164
|
+
parsed.searchParams.forEach((value, key) => {
|
|
165
|
+
params[key] = value;
|
|
166
|
+
});
|
|
167
|
+
return params;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error("[ExpoURLParamsAccessor] Failed to parse URL", url, error);
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
dispose() {
|
|
174
|
+
this.stopListening();
|
|
175
|
+
this.listeners.clear();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/providers/embedded/logger.ts
|
|
180
|
+
var ExpoLogger = class {
|
|
181
|
+
constructor(enabled = false) {
|
|
182
|
+
this.enabled = enabled;
|
|
183
|
+
}
|
|
184
|
+
info(category, message, data) {
|
|
185
|
+
if (this.enabled) {
|
|
186
|
+
console.info(`[${category}] ${message}`, data);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
warn(category, message, data) {
|
|
190
|
+
if (this.enabled) {
|
|
191
|
+
console.warn(`[${category}] ${message}`, data);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
error(category, message, data) {
|
|
195
|
+
if (this.enabled) {
|
|
196
|
+
console.error(`[${category}] ${message}`, data);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
log(category, message, data) {
|
|
200
|
+
if (this.enabled) {
|
|
201
|
+
console.log(`[${category}] ${message}`, data);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/PhantomProvider.tsx
|
|
207
|
+
import { jsx } from "react/jsx-runtime";
|
|
208
|
+
var PhantomContext = createContext(void 0);
|
|
209
|
+
function PhantomProvider({ children, config }) {
|
|
210
|
+
const sdk = useMemo(() => {
|
|
211
|
+
const redirectUrl = config.authOptions?.redirectUrl || `${config.scheme}://phantom-auth-callback`;
|
|
212
|
+
const embeddedConfig = {
|
|
213
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
214
|
+
organizationId: config.organizationId,
|
|
215
|
+
authOptions: {
|
|
216
|
+
...config.authOptions,
|
|
217
|
+
redirectUrl
|
|
218
|
+
},
|
|
219
|
+
embeddedWalletType: config.embeddedWalletType,
|
|
220
|
+
addressTypes: config.addressTypes,
|
|
221
|
+
solanaProvider: config.solanaProvider || "web3js"
|
|
222
|
+
};
|
|
223
|
+
const storage = new ExpoSecureStorage();
|
|
224
|
+
const authProvider = new ExpoAuthProvider();
|
|
225
|
+
const urlParamsAccessor = new ExpoURLParamsAccessor();
|
|
226
|
+
const logger = new ExpoLogger(config.debug);
|
|
227
|
+
const platform = {
|
|
228
|
+
storage,
|
|
229
|
+
authProvider,
|
|
230
|
+
urlParamsAccessor
|
|
231
|
+
};
|
|
232
|
+
return new EmbeddedProvider(embeddedConfig, platform, logger);
|
|
233
|
+
}, [config]);
|
|
234
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
235
|
+
const [addresses, setAddresses] = useState([]);
|
|
236
|
+
const [walletId, setWalletId] = useState(null);
|
|
237
|
+
const [error, setError] = useState(null);
|
|
238
|
+
const updateConnectionState = useCallback(() => {
|
|
239
|
+
try {
|
|
240
|
+
const connected = sdk.isConnected();
|
|
241
|
+
setIsConnected(connected);
|
|
242
|
+
if (connected) {
|
|
243
|
+
const addrs = sdk.getAddresses();
|
|
244
|
+
setAddresses(addrs);
|
|
245
|
+
} else {
|
|
246
|
+
setAddresses([]);
|
|
247
|
+
setWalletId(null);
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error("[PhantomProvider] Error updating connection state", err);
|
|
251
|
+
setError(err);
|
|
252
|
+
}
|
|
253
|
+
}, [sdk]);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
updateConnectionState();
|
|
256
|
+
}, [updateConnectionState]);
|
|
257
|
+
const value = {
|
|
258
|
+
sdk,
|
|
259
|
+
isConnected,
|
|
260
|
+
addresses,
|
|
261
|
+
walletId,
|
|
262
|
+
error,
|
|
263
|
+
updateConnectionState,
|
|
264
|
+
setWalletId
|
|
265
|
+
};
|
|
266
|
+
return /* @__PURE__ */ jsx(PhantomContext.Provider, { value, children });
|
|
267
|
+
}
|
|
268
|
+
function usePhantom() {
|
|
269
|
+
const context = useContext(PhantomContext);
|
|
270
|
+
if (!context) {
|
|
271
|
+
throw new Error("usePhantom must be used within a PhantomProvider");
|
|
272
|
+
}
|
|
273
|
+
return context;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/hooks/useConnect.ts
|
|
277
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
278
|
+
function useConnect() {
|
|
279
|
+
const { sdk, updateConnectionState, setWalletId } = usePhantom();
|
|
280
|
+
const [isConnecting, setIsConnecting] = useState2(false);
|
|
281
|
+
const [error, setError] = useState2(null);
|
|
282
|
+
const connect = useCallback2(
|
|
283
|
+
async (options) => {
|
|
284
|
+
if (!sdk) {
|
|
285
|
+
throw new Error("SDK not initialized");
|
|
286
|
+
}
|
|
287
|
+
setIsConnecting(true);
|
|
288
|
+
setError(null);
|
|
289
|
+
try {
|
|
290
|
+
const result = await sdk.connect(options);
|
|
291
|
+
if (result.status === "completed") {
|
|
292
|
+
if (result.walletId) {
|
|
293
|
+
setWalletId(result.walletId);
|
|
294
|
+
}
|
|
295
|
+
updateConnectionState();
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
} catch (err) {
|
|
299
|
+
const error2 = err;
|
|
300
|
+
setError(error2);
|
|
301
|
+
throw error2;
|
|
302
|
+
} finally {
|
|
303
|
+
setIsConnecting(false);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
[sdk, updateConnectionState, setWalletId]
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
connect,
|
|
310
|
+
isConnecting,
|
|
311
|
+
error
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/hooks/useDisconnect.ts
|
|
316
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
317
|
+
function useDisconnect() {
|
|
318
|
+
const { sdk, updateConnectionState } = usePhantom();
|
|
319
|
+
const [isDisconnecting, setIsDisconnecting] = useState3(false);
|
|
320
|
+
const [error, setError] = useState3(null);
|
|
321
|
+
const disconnect = useCallback3(async () => {
|
|
322
|
+
if (!sdk) {
|
|
323
|
+
throw new Error("SDK not initialized");
|
|
324
|
+
}
|
|
325
|
+
setIsDisconnecting(true);
|
|
326
|
+
setError(null);
|
|
327
|
+
try {
|
|
328
|
+
await sdk.disconnect();
|
|
329
|
+
updateConnectionState();
|
|
330
|
+
} catch (err) {
|
|
331
|
+
const error2 = err;
|
|
332
|
+
setError(error2);
|
|
333
|
+
throw error2;
|
|
334
|
+
} finally {
|
|
335
|
+
setIsDisconnecting(false);
|
|
336
|
+
}
|
|
337
|
+
}, [sdk, updateConnectionState]);
|
|
338
|
+
return {
|
|
339
|
+
disconnect,
|
|
340
|
+
isDisconnecting,
|
|
341
|
+
error
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/hooks/useAccounts.ts
|
|
346
|
+
function useAccounts() {
|
|
347
|
+
const { addresses, isConnected, walletId, error } = usePhantom();
|
|
348
|
+
return {
|
|
349
|
+
addresses,
|
|
350
|
+
isConnected,
|
|
351
|
+
walletId,
|
|
352
|
+
error
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/hooks/useSignMessage.ts
|
|
357
|
+
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
358
|
+
function useSignMessage() {
|
|
359
|
+
const { sdk } = usePhantom();
|
|
360
|
+
const [isSigning, setIsSigning] = useState4(false);
|
|
361
|
+
const [error, setError] = useState4(null);
|
|
362
|
+
const signMessage = useCallback4(
|
|
363
|
+
async (params) => {
|
|
364
|
+
if (!sdk) {
|
|
365
|
+
throw new Error("SDK not initialized");
|
|
366
|
+
}
|
|
367
|
+
setIsSigning(true);
|
|
368
|
+
setError(null);
|
|
369
|
+
try {
|
|
370
|
+
const signature = await sdk.signMessage(params);
|
|
371
|
+
return signature;
|
|
372
|
+
} catch (err) {
|
|
373
|
+
const error2 = err;
|
|
374
|
+
setError(error2);
|
|
375
|
+
throw error2;
|
|
376
|
+
} finally {
|
|
377
|
+
setIsSigning(false);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
[sdk]
|
|
381
|
+
);
|
|
382
|
+
return {
|
|
383
|
+
signMessage,
|
|
384
|
+
isSigning,
|
|
385
|
+
error
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/hooks/useSignAndSendTransaction.ts
|
|
390
|
+
import { useState as useState5, useCallback as useCallback5 } from "react";
|
|
391
|
+
function useSignAndSendTransaction() {
|
|
392
|
+
const { sdk } = usePhantom();
|
|
393
|
+
const [isSigning, setIsSigning] = useState5(false);
|
|
394
|
+
const [error, setError] = useState5(null);
|
|
395
|
+
const signAndSendTransaction = useCallback5(
|
|
396
|
+
async (params) => {
|
|
397
|
+
if (!sdk) {
|
|
398
|
+
throw new Error("SDK not initialized");
|
|
399
|
+
}
|
|
400
|
+
setIsSigning(true);
|
|
401
|
+
setError(null);
|
|
402
|
+
try {
|
|
403
|
+
const result = await sdk.signAndSendTransaction(params);
|
|
404
|
+
return result;
|
|
405
|
+
} catch (err) {
|
|
406
|
+
const error2 = err;
|
|
407
|
+
setError(error2);
|
|
408
|
+
throw error2;
|
|
409
|
+
} finally {
|
|
410
|
+
setIsSigning(false);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
[sdk]
|
|
414
|
+
);
|
|
415
|
+
return {
|
|
416
|
+
signAndSendTransaction,
|
|
417
|
+
isSigning,
|
|
418
|
+
error
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/index.ts
|
|
423
|
+
import { AddressType, NetworkId } from "@phantom/client";
|
|
424
|
+
export {
|
|
425
|
+
AddressType,
|
|
426
|
+
NetworkId,
|
|
427
|
+
PhantomProvider,
|
|
428
|
+
useAccounts,
|
|
429
|
+
useConnect,
|
|
430
|
+
useDisconnect,
|
|
431
|
+
usePhantom,
|
|
432
|
+
useSignAndSendTransaction,
|
|
433
|
+
useSignMessage
|
|
434
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phantom/react-native-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Phantom Wallet SDK for React Native and Expo applications",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"?pack-release": "When https://github.com/changesets/changesets/issues/432 has a solution we can remove this trick",
|
|
21
|
+
"pack-release": "rimraf ./_release && yarn pack && mkdir ./_release && tar zxvf ./package.tgz --directory ./_release && rm ./package.tgz",
|
|
22
|
+
"build": "rimraf ./dist && tsup",
|
|
23
|
+
"dev": "rimraf ./dist && tsup --watch",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"test:watch": "jest --watch",
|
|
27
|
+
"lint": "tsc --noEmit && eslint --cache . --ext .ts,.tsx",
|
|
28
|
+
"check-types": "tsc --noEmit",
|
|
29
|
+
"prettier": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"phantom",
|
|
33
|
+
"wallet",
|
|
34
|
+
"react-native",
|
|
35
|
+
"expo",
|
|
36
|
+
"solana",
|
|
37
|
+
"ethereum",
|
|
38
|
+
"crypto",
|
|
39
|
+
"web3"
|
|
40
|
+
],
|
|
41
|
+
"author": "Phantom",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/phantom/wallet-sdk.git",
|
|
45
|
+
"directory": "packages/react-native-sdk"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@phantom/client": "^0.1.3",
|
|
49
|
+
"@phantom/embedded-provider-core": "^0.1.1"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"expo": ">=53.0.0",
|
|
53
|
+
"expo-auth-session": ">=5.0.0",
|
|
54
|
+
"expo-secure-store": ">=12.0.0",
|
|
55
|
+
"expo-web-browser": ">=12.0.0",
|
|
56
|
+
"react": ">=19.0.0",
|
|
57
|
+
"react-native": ">=0.79.0",
|
|
58
|
+
"react-native-get-random-values": ">=1.8.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/jest": "^29.5.14",
|
|
62
|
+
"@types/react": "~19.0.10",
|
|
63
|
+
"@types/react-native": "^0.72.0",
|
|
64
|
+
"eslint": "8.53.0",
|
|
65
|
+
"eslint-define-config": "^1.24.1",
|
|
66
|
+
"eslint-plugin-react": "^7.33.0",
|
|
67
|
+
"eslint-plugin-react-native": "^4.1.0",
|
|
68
|
+
"expo": "^53.0.20",
|
|
69
|
+
"expo-auth-session": "^6.2.1",
|
|
70
|
+
"expo-secure-store": "^14.2.3",
|
|
71
|
+
"expo-web-browser": "^14.2.0",
|
|
72
|
+
"jest": "^29.7.0",
|
|
73
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
74
|
+
"prettier": "^3.5.2",
|
|
75
|
+
"react": "19.1.1",
|
|
76
|
+
"react-native": "0.79.5",
|
|
77
|
+
"rimraf": "^6.0.1",
|
|
78
|
+
"ts-jest": "^29",
|
|
79
|
+
"tsup": "^6.7.0",
|
|
80
|
+
"typescript": "^5.8.3"
|
|
81
|
+
},
|
|
82
|
+
"publishConfig": {
|
|
83
|
+
"directory": "_release/package"
|
|
84
|
+
}
|
|
85
|
+
}
|