@croacroa/react-native-template 2.0.1 → 3.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/.env.example +5 -0
- package/.eslintrc.js +8 -0
- package/.github/workflows/ci.yml +187 -187
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/.github/workflows/npm-publish.yml +57 -0
- package/CHANGELOG.md +195 -106
- package/CONTRIBUTING.md +377 -377
- package/LICENSE +21 -0
- package/README.md +446 -399
- package/__tests__/accessibility/components.test.tsx +285 -0
- package/__tests__/components/Button.test.tsx +2 -4
- package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
- package/__tests__/components/snapshots.test.tsx +131 -131
- package/__tests__/helpers/a11y.ts +54 -0
- package/__tests__/hooks/useAnalytics.test.ts +100 -0
- package/__tests__/hooks/useAnimations.test.ts +70 -0
- package/__tests__/hooks/useAuth.test.tsx +71 -28
- package/__tests__/hooks/useMedia.test.ts +318 -0
- package/__tests__/hooks/usePayments.test.tsx +307 -0
- package/__tests__/hooks/usePermission.test.ts +230 -0
- package/__tests__/hooks/useWebSocket.test.ts +329 -0
- package/__tests__/integration/auth-api.test.tsx +224 -227
- package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
- package/__tests__/services/api.test.ts +24 -6
- package/app/(auth)/home.tsx +11 -9
- package/app/(auth)/profile.tsx +8 -6
- package/app/(auth)/settings.tsx +11 -9
- package/app/(public)/forgot-password.tsx +25 -15
- package/app/(public)/login.tsx +48 -12
- package/app/(public)/onboarding.tsx +5 -5
- package/app/(public)/register.tsx +24 -15
- package/app/_layout.tsx +6 -3
- package/app.config.ts +27 -2
- package/assets/images/.gitkeep +7 -7
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/notification-icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/components/ErrorBoundary.tsx +73 -28
- package/components/auth/SocialLoginButtons.tsx +168 -0
- package/components/forms/FormInput.tsx +5 -3
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/AnalyticsProvider.tsx +67 -0
- package/components/providers/SuspenseBoundary.tsx +359 -357
- package/components/providers/index.ts +24 -21
- package/components/ui/AnimatedButton.tsx +1 -9
- package/components/ui/AnimatedList.tsx +98 -0
- package/components/ui/AnimatedScreen.tsx +89 -0
- package/components/ui/Avatar.tsx +319 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Button.tsx +11 -3
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/FeatureGate.tsx +57 -0
- package/components/ui/ForceUpdateScreen.tsx +108 -0
- package/components/ui/ImagePickerButton.tsx +180 -0
- package/components/ui/Input.stories.tsx +2 -10
- package/components/ui/Input.tsx +2 -10
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Paywall.tsx +253 -0
- package/components/ui/PermissionGate.tsx +155 -0
- package/components/ui/PurchaseButton.tsx +84 -0
- package/components/ui/Select.tsx +240 -240
- package/components/ui/Skeleton.tsx +3 -1
- package/components/ui/Toast.tsx +427 -0
- package/components/ui/UploadProgress.tsx +189 -0
- package/components/ui/VirtualizedList.tsx +288 -285
- package/components/ui/index.ts +28 -23
- package/constants/config.ts +135 -97
- package/docs/adr/001-state-management.md +79 -79
- package/docs/adr/002-styling-approach.md +130 -130
- package/docs/adr/003-data-fetching.md +155 -155
- package/docs/adr/004-auth-adapter-pattern.md +144 -144
- package/docs/adr/README.md +78 -78
- package/docs/guides/analytics-posthog.md +121 -0
- package/docs/guides/auth-supabase.md +162 -0
- package/docs/guides/feature-flags-launchdarkly.md +150 -0
- package/docs/guides/payments-revenuecat.md +169 -0
- package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
- package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
- package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
- package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
- package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
- package/eas.json +2 -1
- package/hooks/index.ts +70 -27
- package/hooks/useAnimatedEntry.ts +204 -0
- package/hooks/useApi.ts +64 -4
- package/hooks/useAuth.tsx +7 -3
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useChannel.ts +111 -0
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useExperiment.ts +36 -0
- package/hooks/useFeatureFlag.ts +59 -0
- package/hooks/useForceUpdate.ts +91 -0
- package/hooks/useImagePicker.ts +281 -0
- package/hooks/useInAppReview.ts +64 -0
- package/hooks/useMFA.ts +509 -499
- package/hooks/useParallax.ts +142 -0
- package/hooks/usePerformance.ts +434 -434
- package/hooks/usePermission.ts +190 -0
- package/hooks/usePresence.ts +129 -0
- package/hooks/useProducts.ts +36 -0
- package/hooks/usePurchase.ts +103 -0
- package/hooks/useRateLimit.ts +70 -0
- package/hooks/useSubscription.ts +49 -0
- package/hooks/useTrackEvent.ts +52 -0
- package/hooks/useTrackScreen.ts +40 -0
- package/hooks/useUpdates.ts +358 -358
- package/hooks/useUpload.ts +165 -0
- package/hooks/useWebSocket.ts +111 -0
- package/i18n/index.ts +197 -194
- package/i18n/locales/ar.json +170 -101
- package/i18n/locales/de.json +170 -101
- package/i18n/locales/en.json +170 -101
- package/i18n/locales/es.json +170 -101
- package/i18n/locales/fr.json +170 -101
- package/jest.config.js +1 -1
- package/maestro/README.md +113 -113
- package/maestro/config.yaml +35 -35
- package/maestro/flows/login.yaml +62 -62
- package/maestro/flows/mfa-login.yaml +92 -92
- package/maestro/flows/mfa-setup.yaml +86 -86
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -101
- package/maestro/flows/offline-sync.yaml +128 -128
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +188 -175
- package/scripts/generate-placeholders.js +38 -0
- package/services/analytics/adapters/console.ts +50 -0
- package/services/analytics/analytics-adapter.ts +94 -0
- package/services/analytics/types.ts +73 -0
- package/services/analytics.ts +428 -428
- package/services/api.ts +419 -340
- package/services/auth/social/apple.ts +110 -0
- package/services/auth/social/google.ts +159 -0
- package/services/auth/social/social-auth.ts +100 -0
- package/services/auth/social/types.ts +80 -0
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +652 -626
- package/services/feature-flags/adapters/mock.ts +108 -0
- package/services/feature-flags/feature-flag-adapter.ts +174 -0
- package/services/feature-flags/types.ts +79 -0
- package/services/force-update.ts +140 -0
- package/services/index.ts +116 -54
- package/services/media/compression.ts +91 -0
- package/services/media/media-picker.ts +151 -0
- package/services/media/media-upload.ts +160 -0
- package/services/payments/adapters/mock.ts +159 -0
- package/services/payments/payment-adapter.ts +118 -0
- package/services/payments/types.ts +131 -0
- package/services/permissions/permission-manager.ts +284 -0
- package/services/permissions/types.ts +104 -0
- package/services/realtime/types.ts +100 -0
- package/services/realtime/websocket-manager.ts +441 -0
- package/services/security.ts +289 -286
- package/services/sentry.ts +4 -4
- package/stores/appStore.ts +9 -0
- package/stores/notificationStore.ts +3 -1
- package/tailwind.config.js +47 -47
- package/tsconfig.json +37 -13
- package/types/user.ts +1 -1
- package/utils/accessibility.ts +446 -446
- package/utils/animations/presets.ts +182 -0
- package/utils/animations/transitions.ts +62 -0
- package/utils/index.ts +63 -52
- package/utils/toast.ts +9 -2
- package/utils/validation.ts +4 -1
- package/utils/withAccessibility.tsx +272 -272
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Apple Sign-In adapter using expo-apple-authentication
|
|
3
|
+
* Provides native Apple Sign-In on iOS devices.
|
|
4
|
+
* @module services/auth/social/apple
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Platform } from "react-native";
|
|
8
|
+
import * as AppleAuthentication from "expo-apple-authentication";
|
|
9
|
+
|
|
10
|
+
import type { SocialAuthResult } from "./types";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Availability Check
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether Apple Sign-In is available on the current device.
|
|
18
|
+
* Apple Sign-In is only available on iOS 13+.
|
|
19
|
+
*
|
|
20
|
+
* @returns true if the device supports Apple Sign-In
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* if (isAppleSignInAvailable()) {
|
|
25
|
+
* // Show Apple Sign-In button
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function isAppleSignInAvailable(): boolean {
|
|
30
|
+
return Platform.OS === "ios";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Apple Sign-In
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Initiates the native Apple Sign-In flow.
|
|
39
|
+
*
|
|
40
|
+
* Requests the user's full name and email on first sign-in.
|
|
41
|
+
* Note that Apple only provides the user's name on the FIRST sign-in;
|
|
42
|
+
* subsequent sign-ins will not include the name.
|
|
43
|
+
*
|
|
44
|
+
* @returns SocialAuthResult on success, null if the user cancelled
|
|
45
|
+
* @throws Error if Apple Sign-In is not available or fails unexpectedly
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* if (isAppleSignInAvailable()) {
|
|
50
|
+
* const result = await signInWithApple();
|
|
51
|
+
* if (result) {
|
|
52
|
+
* console.log('Signed in as:', result.user.email);
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export async function signInWithApple(): Promise<SocialAuthResult | null> {
|
|
58
|
+
if (!isAppleSignInAvailable()) {
|
|
59
|
+
throw new Error("Apple Sign-In is only available on iOS devices");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const credential = await AppleAuthentication.signInAsync({
|
|
64
|
+
requestedScopes: [
|
|
65
|
+
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
66
|
+
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const { identityToken, user, fullName, email } = credential;
|
|
71
|
+
|
|
72
|
+
if (!identityToken) {
|
|
73
|
+
throw new Error("Apple Sign-In did not return an identity token");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Build display name from fullName components
|
|
77
|
+
// Apple only provides the name on the first sign-in
|
|
78
|
+
const nameParts: string[] = [];
|
|
79
|
+
if (fullName?.givenName) {
|
|
80
|
+
nameParts.push(fullName.givenName);
|
|
81
|
+
}
|
|
82
|
+
if (fullName?.familyName) {
|
|
83
|
+
nameParts.push(fullName.familyName);
|
|
84
|
+
}
|
|
85
|
+
const displayName = nameParts.length > 0 ? nameParts.join(" ") : "";
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
provider: "apple",
|
|
89
|
+
idToken: identityToken,
|
|
90
|
+
user: {
|
|
91
|
+
id: user,
|
|
92
|
+
email: email ?? "",
|
|
93
|
+
name: displayName,
|
|
94
|
+
avatar: undefined,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
} catch (error: unknown) {
|
|
98
|
+
// Handle user cancellation gracefully
|
|
99
|
+
if (
|
|
100
|
+
error &&
|
|
101
|
+
typeof error === "object" &&
|
|
102
|
+
"code" in error &&
|
|
103
|
+
(error as { code: string }).code === "ERR_REQUEST_CANCELED"
|
|
104
|
+
) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Google Sign-In adapter using expo-auth-session
|
|
3
|
+
* Implements OAuth 2.0 with PKCE for secure Google authentication.
|
|
4
|
+
* @module services/auth/social/google
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as WebBrowser from "expo-web-browser";
|
|
8
|
+
import {
|
|
9
|
+
makeRedirectUri,
|
|
10
|
+
AuthRequest,
|
|
11
|
+
exchangeCodeAsync,
|
|
12
|
+
type AuthSessionResult,
|
|
13
|
+
} from "expo-auth-session";
|
|
14
|
+
import { Platform } from "react-native";
|
|
15
|
+
|
|
16
|
+
import type { SocialAuthResult, GoogleSignInOptions } from "./types";
|
|
17
|
+
|
|
18
|
+
// Required for web-based auth session completion
|
|
19
|
+
WebBrowser.maybeCompleteAuthSession();
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Google OAuth Discovery Document
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Google OAuth 2.0 endpoint configuration.
|
|
27
|
+
* @see https://accounts.google.com/.well-known/openid-configuration
|
|
28
|
+
*/
|
|
29
|
+
const GOOGLE_DISCOVERY = {
|
|
30
|
+
authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
31
|
+
tokenEndpoint: "https://oauth2.googleapis.com/token",
|
|
32
|
+
revocationEndpoint: "https://oauth2.googleapis.com/revoke",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// JWT Decoding
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decodes a JWT token payload without verification.
|
|
41
|
+
* Used to extract user information from the Google ID token.
|
|
42
|
+
*
|
|
43
|
+
* NOTE: In production, you should verify the token on your backend.
|
|
44
|
+
* This client-side decode is only for extracting display information.
|
|
45
|
+
*/
|
|
46
|
+
function decodeJwtPayload(token: string): Record<string, unknown> {
|
|
47
|
+
try {
|
|
48
|
+
const parts = token.split(".");
|
|
49
|
+
if (parts.length !== 3) {
|
|
50
|
+
throw new Error("Invalid JWT format");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const payload = parts[1];
|
|
54
|
+
// Handle base64url encoding (replace URL-safe chars and pad)
|
|
55
|
+
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
56
|
+
const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
|
|
57
|
+
const decoded = atob(padded);
|
|
58
|
+
|
|
59
|
+
return JSON.parse(decoded);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error("Failed to decode JWT token");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Google Sign-In
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initiates a Google Sign-In flow using expo-auth-session with PKCE.
|
|
71
|
+
*
|
|
72
|
+
* @param options - Google sign-in configuration options
|
|
73
|
+
* @returns SocialAuthResult on success, null if the user cancelled
|
|
74
|
+
* @throws Error if the sign-in flow fails
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const result = await signInWithGoogle({
|
|
79
|
+
* clientId: 'your-client-id.apps.googleusercontent.com',
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* if (result) {
|
|
83
|
+
* console.log('Signed in as:', result.user.name);
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export async function signInWithGoogle(
|
|
88
|
+
options: GoogleSignInOptions
|
|
89
|
+
): Promise<SocialAuthResult | null> {
|
|
90
|
+
const clientId = Platform.select({
|
|
91
|
+
ios: options.iosClientId ?? options.clientId,
|
|
92
|
+
android: options.androidClientId ?? options.clientId,
|
|
93
|
+
default: options.clientId,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!clientId) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"Google Sign-In requires a clientId. " +
|
|
99
|
+
"Call SocialAuth.configure({ google: { clientId: '...' } }) first."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const redirectUri = makeRedirectUri({
|
|
104
|
+
scheme: undefined, // Uses Expo's default scheme
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Create auth request with PKCE enabled
|
|
108
|
+
const authRequest = new AuthRequest({
|
|
109
|
+
clientId,
|
|
110
|
+
redirectUri,
|
|
111
|
+
scopes: ["openid", "profile", "email"],
|
|
112
|
+
usePKCE: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Prompt the user to sign in
|
|
116
|
+
const authResult: AuthSessionResult =
|
|
117
|
+
await authRequest.promptAsync(GOOGLE_DISCOVERY);
|
|
118
|
+
|
|
119
|
+
// User cancelled or dismissed the dialog
|
|
120
|
+
if (authResult.type !== "success") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { code } = authResult.params;
|
|
125
|
+
|
|
126
|
+
// Exchange authorization code for tokens
|
|
127
|
+
const tokenResult = await exchangeCodeAsync(
|
|
128
|
+
{
|
|
129
|
+
clientId,
|
|
130
|
+
code,
|
|
131
|
+
redirectUri,
|
|
132
|
+
extraParams: {
|
|
133
|
+
code_verifier: authRequest.codeVerifier ?? "",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
GOOGLE_DISCOVERY
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const { idToken, accessToken } = tokenResult;
|
|
140
|
+
|
|
141
|
+
if (!idToken) {
|
|
142
|
+
throw new Error("Google Sign-In did not return an ID token");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Decode user info from the ID token
|
|
146
|
+
const payload = decodeJwtPayload(idToken);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
provider: "google",
|
|
150
|
+
idToken,
|
|
151
|
+
accessToken: accessToken ?? undefined,
|
|
152
|
+
user: {
|
|
153
|
+
id: (payload.sub as string) ?? "",
|
|
154
|
+
email: (payload.email as string) ?? "",
|
|
155
|
+
name: (payload.name as string) ?? "",
|
|
156
|
+
avatar: (payload.picture as string) ?? undefined,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Social authentication orchestrator
|
|
3
|
+
* Provides a unified API for Google and Apple sign-in flows.
|
|
4
|
+
* Configure once, then call SocialAuth.signIn(provider) from anywhere.
|
|
5
|
+
* @module services/auth/social/social-auth
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { signInWithGoogle } from "./google";
|
|
9
|
+
import { signInWithApple } from "./apple";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
SocialProvider,
|
|
13
|
+
SocialAuthResult,
|
|
14
|
+
SocialAuthConfig,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
// Re-export commonly used types and utilities
|
|
18
|
+
export { isAppleSignInAvailable } from "./apple";
|
|
19
|
+
export type { SocialAuthResult, SocialProvider } from "./types";
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Configuration
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Internal configuration state.
|
|
27
|
+
* Set via SocialAuth.configure() before calling signIn().
|
|
28
|
+
*/
|
|
29
|
+
let config: SocialAuthConfig = {};
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Social Auth API
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Unified social authentication API.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Configure (typically in app startup)
|
|
41
|
+
* SocialAuth.configure({
|
|
42
|
+
* google: {
|
|
43
|
+
* clientId: 'your-client-id.apps.googleusercontent.com',
|
|
44
|
+
* iosClientId: 'your-ios-client-id.apps.googleusercontent.com',
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Sign in with a provider
|
|
49
|
+
* const result = await SocialAuth.signIn('google');
|
|
50
|
+
* if (result) {
|
|
51
|
+
* // Send result.idToken to your backend for verification
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const SocialAuth = {
|
|
56
|
+
/**
|
|
57
|
+
* Configure social auth providers.
|
|
58
|
+
* Call this once during app initialization (e.g., in _layout.tsx).
|
|
59
|
+
*
|
|
60
|
+
* @param newConfig - Provider configuration
|
|
61
|
+
*/
|
|
62
|
+
configure(newConfig: SocialAuthConfig): void {
|
|
63
|
+
config = { ...config, ...newConfig };
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initiate sign-in with the specified social provider.
|
|
68
|
+
*
|
|
69
|
+
* @param provider - The social provider to sign in with ('google' | 'apple')
|
|
70
|
+
* @returns SocialAuthResult on success, null if the user cancelled
|
|
71
|
+
* @throws Error if the provider is not configured or sign-in fails
|
|
72
|
+
*/
|
|
73
|
+
async signIn(provider: SocialProvider): Promise<SocialAuthResult | null> {
|
|
74
|
+
switch (provider) {
|
|
75
|
+
case "google": {
|
|
76
|
+
if (!config.google?.clientId) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Google Sign-In is not configured. " +
|
|
79
|
+
"Call SocialAuth.configure({ google: { clientId: '...' } }) first."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return signInWithGoogle({
|
|
84
|
+
clientId: config.google.clientId,
|
|
85
|
+
iosClientId: config.google.iosClientId,
|
|
86
|
+
androidClientId: config.google.androidClientId,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case "apple": {
|
|
91
|
+
return signInWithApple();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
default: {
|
|
95
|
+
const _exhaustive: never = provider;
|
|
96
|
+
throw new Error(`Unknown social provider: ${_exhaustive}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Social authentication type definitions
|
|
3
|
+
* Provides shared types for Google and Apple sign-in adapters.
|
|
4
|
+
* @module services/auth/social/types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supported social authentication providers.
|
|
9
|
+
*/
|
|
10
|
+
export type SocialProvider = "google" | "apple";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* User information returned from a social sign-in flow.
|
|
14
|
+
*/
|
|
15
|
+
export interface SocialUser {
|
|
16
|
+
/** Unique user ID from the provider */
|
|
17
|
+
id: string;
|
|
18
|
+
/** User's email address */
|
|
19
|
+
email: string;
|
|
20
|
+
/** User's display name */
|
|
21
|
+
name: string;
|
|
22
|
+
/** URL to the user's avatar/profile picture */
|
|
23
|
+
avatar?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Result of a successful social authentication.
|
|
28
|
+
* Contains the provider identifier, tokens, and user info.
|
|
29
|
+
*/
|
|
30
|
+
export interface SocialAuthResult {
|
|
31
|
+
/** The social provider used for authentication */
|
|
32
|
+
provider: SocialProvider;
|
|
33
|
+
/** JWT ID token from the provider */
|
|
34
|
+
idToken: string;
|
|
35
|
+
/** OAuth access token (not always available, e.g., Apple) */
|
|
36
|
+
accessToken?: string;
|
|
37
|
+
/** Authenticated user information */
|
|
38
|
+
user: SocialUser;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for Google Sign-In.
|
|
43
|
+
*/
|
|
44
|
+
export interface GoogleAuthConfig {
|
|
45
|
+
/** Web client ID (required for expo-auth-session) */
|
|
46
|
+
clientId: string;
|
|
47
|
+
/** iOS-specific client ID (optional, falls back to clientId) */
|
|
48
|
+
iosClientId?: string;
|
|
49
|
+
/** Android-specific client ID (optional, falls back to clientId) */
|
|
50
|
+
androidClientId?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Configuration for Apple Sign-In.
|
|
55
|
+
* Apple Sign-In requires no additional configuration beyond platform support.
|
|
56
|
+
*/
|
|
57
|
+
export type AppleAuthConfig = Record<string, never>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Combined social auth configuration.
|
|
61
|
+
* Pass to SocialAuth.configure() to set up providers.
|
|
62
|
+
*/
|
|
63
|
+
export interface SocialAuthConfig {
|
|
64
|
+
/** Google Sign-In configuration */
|
|
65
|
+
google?: GoogleAuthConfig;
|
|
66
|
+
/** Apple Sign-In configuration */
|
|
67
|
+
apple?: AppleAuthConfig;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Options passed to the Google sign-in function.
|
|
72
|
+
*/
|
|
73
|
+
export interface GoogleSignInOptions {
|
|
74
|
+
/** Web client ID (overrides configured value) */
|
|
75
|
+
clientId?: string;
|
|
76
|
+
/** iOS-specific client ID (overrides configured value) */
|
|
77
|
+
iosClientId?: string;
|
|
78
|
+
/** Android-specific client ID (overrides configured value) */
|
|
79
|
+
androidClientId?: string;
|
|
80
|
+
}
|