Skip to content

Commit 334c835

Browse files
authored
Merge branch 'main' into jacek/express-forward-auth-options
2 parents ce206c5 + d7317c1 commit 334c835

11 files changed

Lines changed: 588 additions & 937 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@clerk/expo": patch
3+
---
4+
5+
Fix `MissingActivity` error on cold-start Google sign-in / passkey flows. Previously, the first tap on "Sign in with Google" in `<AuthView />` failed with `Clerk error: Google sign-in cannot start: Credential Manager requires an active Activity context.` — the workaround was to background and foreground the app once before signing in.
6+
7+
The Android bridge now calls `Clerk.attachActivity()` (added in clerk-android 1.0.16) at SDK initialization and on AuthView/UserProfile mount, so the current Activity is registered with the underlying SDK before any Credential Manager call. No app-side changes required; the fix is transparent on rebuild.

.changeset/pink-taxes-do.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/ui': patch
3+
---
4+
5+
Remove back button on the sign-in password compromised/pwned error screen.
6+
7+
These errors are not recoverable by re-entering the password, so the back button led to a confusing dead end that would always take you back to the same error.

packages/backend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@
125125
},
126126
"devDependencies": {
127127
"@edge-runtime/vm": "5.0.0",
128-
"cookie": "1.0.2",
129-
"msw": "2.11.6",
128+
"cookie": "1.1.1",
129+
"msw": "2.14.2",
130130
"npm-run-all": "^4.1.5",
131131
"snakecase-keys": "9.0.2",
132132
"vitest-environment-miniflare": "2.14.4"

packages/clerk-js/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@
103103
"devDependencies": {
104104
"@clerk/msw": "workspace:^",
105105
"@clerk/testing": "workspace:^",
106-
"@emotion/react": "11.11.1",
106+
"@emotion/react": "11.14.0",
107107
"@rsdoctor/rspack-plugin": "^0.4.13",
108108
"@rspack/cli": "catalog:rspack",
109109
"@rspack/core": "catalog:rspack",
110110
"@rspack/plugin-react-refresh": "catalog:rspack",
111111
"@types/cloudflare-turnstile": "^0.2.2",
112112
"@types/webpack-env": "^1.18.8",
113-
"bundlewatch": "^0.4.1",
113+
"bundlewatch": "^0.4.2",
114114
"jsdom": "26.1.0",
115-
"minimatch": "^10.0.3",
115+
"minimatch": "^10.2.5",
116116
"webpack-merge": "^5.10.0"
117117
},
118118
"engines": {

packages/expo/android/build.gradle

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ ext {
1818
credentialsVersion = "1.3.0"
1919
googleIdVersion = "1.1.1"
2020
kotlinxCoroutinesVersion = "1.7.3"
21-
clerkAndroidApiVersion = "1.0.13"
22-
clerkAndroidUiVersion = "1.0.13"
21+
clerkAndroidApiVersion = "1.0.16"
22+
clerkAndroidUiVersion = "1.0.16"
2323
composeVersion = "1.7.0"
2424
activityComposeVersion = "1.9.0"
2525
lifecycleVersion = "2.8.0"
@@ -117,6 +117,16 @@ dependencies {
117117
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
118118
exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection'
119119
}
120+
// clerk-android-telemetry has a transitive dep on the last released
121+
// clerk-android-api. Pinning the api explicitly here keeps consumers
122+
// compiling against the same version we ship the UI from.
123+
implementation("com.clerk:clerk-android-api:$clerkAndroidApiVersion") {
124+
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
125+
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
126+
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
127+
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
128+
exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection'
129+
}
120130

121131
// Jetpack Compose for wrapping Clerk views
122132
implementation "androidx.compose.ui:ui:$composeVersion"

packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthExpoView.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ class ClerkAuthNativeView(context: Context) : FrameLayout(context) {
4444
var mode: String = "signInOrUp"
4545
var isDismissable: Boolean = true
4646

47-
private val activity: ComponentActivity? = findActivity(context)
47+
private val activity: ComponentActivity? = findActivity(context).also {
48+
// At cold start, ClerkExpoModule.configure() may run before React's
49+
// host-resume sync — meaning getCurrentActivity() returns null there.
50+
// This view's construction is a reliable second hook: we know the Activity
51+
// is available (we just walked the context to find it) and we're about to
52+
// render Google sign-in / passkey buttons that need it.
53+
if (it != null) Clerk.attachActivity(it)
54+
}
4855

4956
// Per-view ViewModelStoreOwner so the AuthView's ViewModels (including its
5057
// navigation state) are scoped to THIS view instance, not the activity.

packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ class ClerkExpoModule(reactContext: ReactApplicationContext) :
8585
}
8686

8787
Clerk.initialize(reactApplicationContext, pubKey)
88+
// clerk-android registers ActivityLifecycleCallbacks during
89+
// initialize(), but in React Native MainActivity has already passed
90+
// onResume() by the time <ClerkProvider> mounts and we reach this
91+
// line, so the callbacks miss the initial activity. Without seeding,
92+
// the first Credential Manager call (Google sign-in / passkeys)
93+
// fails with MissingActivity until the user backgrounds and
94+
// foregrounds the app. getCurrentActivity() can be null here on
95+
// cold start before React's host-resume sync — AuthView and
96+
// UserProfile also call attachActivity() on mount as a backstop.
97+
getCurrentActivity()?.let { Clerk.attachActivity(it) }
8898
// Theme loading is centralized here. ClerkViewFactory.configure()
8999
// and ClerkUserProfileActivity.onCreate() only call Clerk.initialize()
90100
// when Clerk is not yet initialized, so by the time they run

packages/expo/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,15 @@
124124
"@clerk/expo-passkeys": "workspace:*",
125125
"@expo/config-plugins": "^54.0.4",
126126
"@types/base-64": "^1.0.2",
127-
"esbuild": "^0.25.0",
127+
"esbuild": "^0.28.0",
128128
"expo-apple-authentication": "^7.2.4",
129-
"expo-auth-session": "^5.4.0",
130-
"expo-constants": "^18.0.0",
131-
"expo-crypto": "^15.0.7",
129+
"expo-auth-session": "^5.5.2",
130+
"expo-constants": "^18.0.13",
131+
"expo-crypto": "^15.0.9",
132132
"expo-local-authentication": "^13.8.0",
133133
"expo-secure-store": "^12.8.1",
134134
"expo-web-browser": "^12.8.2",
135-
"react-native": "^0.81.4"
135+
"react-native": "^0.85.2"
136136
},
137137
"peerDependencies": {
138138
"@clerk/expo-passkeys": ">=0.0.6",

packages/ui/src/components/SignIn/SignInFactorOne.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ function SignInFactorOneInternal(): JSX.Element {
155155
}
156156

157157
if (showAllStrategies || showForgotPasswordStrategies) {
158-
const canGoBack = factorHasLocalStrategy(currentFactor);
158+
// Password errors are not recoverable by re-entering the password, so we hide the back button
159+
const canGoBack = factorHasLocalStrategy(currentFactor) && !passwordErrorCode;
159160

160161
const toggle = showAllStrategies ? toggleAllStrategies : toggleForgotPasswordStrategies;
161162
const backHandler = () => {

packages/ui/src/components/SignIn/__tests__/SignInFactorOne.test.tsx

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -303,57 +303,6 @@ describe('SignInFactorOne', () => {
303303
await screen.findByText('First, enter the code sent to your phone');
304304
});
305305

306-
it('entering a pwned password, then going back and clicking forgot password should result in the correct title', async () => {
307-
const { wrapper, fixtures } = await createFixtures(f => {
308-
f.withEmailAddress();
309-
f.withPassword();
310-
f.withPreferredSignInStrategy({ strategy: 'password' });
311-
f.startSignInWithEmailAddress({
312-
supportPassword: true,
313-
supportEmailCode: true,
314-
supportResetPassword: true,
315-
});
316-
});
317-
fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource));
318-
319-
const errJSON = {
320-
code: 'form_password_pwned',
321-
long_message:
322-
'Password has been found in an online data breach. For account safety, please reset your password.',
323-
message: 'Password has been found in an online data breach. For account safety, please reset your password.',
324-
meta: { param_name: 'password' },
325-
};
326-
327-
fixtures.signIn.attemptFirstFactor.mockRejectedValueOnce(
328-
new ClerkAPIResponseError('Error', {
329-
data: [errJSON],
330-
status: 422,
331-
}),
332-
);
333-
334-
const { userEvent } = render(<SignInFactorOne />, { wrapper });
335-
await userEvent.type(screen.getByLabelText('Password'), '123456');
336-
await userEvent.click(screen.getByText('Continue'));
337-
338-
await screen.findByText('Password compromised');
339-
await screen.findByText(
340-
'This password has been found as part of a breach and can not be used, please reset your password.',
341-
);
342-
await screen.findByText('Or, sign in with another method');
343-
344-
// Go back
345-
await userEvent.click(screen.getByText('Back'));
346-
347-
// Choose to reset password via "Forgot password" instead
348-
await userEvent.click(screen.getByText(/Forgot password/i));
349-
await screen.findByText('Forgot Password?');
350-
expect(
351-
screen.queryByText(
352-
'This password has been found as part of a breach and can not be used, please reset your password.',
353-
),
354-
).not.toBeInTheDocument();
355-
});
356-
357306
it('using an compromised password should show the compromised password screen', async () => {
358307
const { wrapper, fixtures } = await createFixtures(f => {
359308
f.withEmailAddress();

0 commit comments

Comments
 (0)