Skip to content

Commit 8c55350

Browse files
authored
fix(desktop): preserve browser sign-in recovery guidance (#8487)
1 parent e72626d commit 8c55350

10 files changed

Lines changed: 39 additions & 14 deletions

File tree

‎apps/desktop/e2e/source-connect.spec.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ test('source authorization returns to its desktop screen and refreshes live', as
110110
const ok = attempts.get(state) === session && url.searchParams.has('code')
111111
attempts.delete(state)
112112
if (ok) installed = true
113-
redirect(`/credential-groups/slack-complete?state=${state}&ok=${ok}`)
113+
const reason =
114+
url.searchParams.get('error') === 'session_expired' ? '&reason=signin_required' : ''
115+
redirect(`/credential-groups/slack-complete?state=${state}&ok=${ok}${reason}`)
114116
return
115117
}
116118
if (
@@ -224,7 +226,7 @@ test('source authorization returns to its desktop screen and refreshes live', as
224226
if (path === '/provider') {
225227
const state = url.searchParams.get('state') ?? ''
226228
response.end(
227-
`<!doctype html><a href="/api/knowledge/slack/oauth/callback?state=${state}&code=fixture">Authorize</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=denied">Cancel</a>`
229+
`<!doctype html><a href="/api/knowledge/slack/oauth/callback?state=${state}&code=fixture">Authorize</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=denied">Cancel</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=session_expired">Session expired</a>`
228230
)
229231
return
230232
}
@@ -389,6 +391,15 @@ test('source authorization returns to its desktop screen and refreshes live', as
389391
await expect(page.getByLabel('Enrollment error')).toHaveText('')
390392
expect(page.url()).toBe(`${origin}/home`)
391393
})
394+
await check('browser sign-in failures retain recovery guidance on desktop', async () => {
395+
await page.getByRole('button', { name: 'Connect Slack' }).click()
396+
await expect.poll(async () => (await opened()).length).toBe(8)
397+
await external.goto((await opened())[7])
398+
await external.getByRole('link', { name: 'Session expired' }).click()
399+
await expect(page.getByLabel('Connection')).toHaveText('error')
400+
await expect(page.getByRole('alert')).toContainText('Sign in to Sim in your browser')
401+
expect(page.url()).toBe(`${origin}/home`)
402+
})
392403
await page.screenshot({ path: test.info().outputPath('source-connect-desktop.png') })
393404
} finally {
394405
mkdirSync(dirname(reportPath), { recursive: true })

‎apps/sim/app/api/knowledge/slack/oauth/callback/route.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('Slack OAuth callback', () => {
8787
const response = await GET(request('state=state&code=code'))
8888
expect(response.status).toBe(303)
8989
expect(response.headers.get('location')).toBe(
90-
'https://www.sim.ai/credential-groups/slack-complete?state=state&ok=false'
90+
'https://www.sim.ai/credential-groups/slack-complete?state=state&ok=false&reason=signin_required'
9191
)
9292
expect(m.authenticate).not.toHaveBeenCalled()
9393
expect(m.complete).not.toHaveBeenCalled()

‎apps/sim/app/api/knowledge/slack/oauth/callback/route.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const GET = withRouteHandler(async (request) => {
7777
const url = new URL('/credential-groups/slack-complete', getBaseUrl())
7878
url.searchParams.set('state', callbackState)
7979
url.searchParams.set('ok', 'false')
80+
if (error instanceof InternalUnauthenticatedError)
81+
url.searchParams.set('reason', 'signin_required')
8082
return NextResponse.redirect(url, {
8183
status: 303,
8284
headers: { 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' },

‎apps/sim/app/credential-groups/complete/completion-handoff.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function CredentialGroupCompletionHandoff({
1717
completionId,
1818
failure,
1919
}: CredentialGroupCompletionHandoffProps) {
20-
const started = useRef(false)
20+
const started = useRef<boolean>(false)
2121
useEffect(() => {
2222
if (started.current) return
2323
started.current = true

‎apps/sim/app/credential-groups/slack-complete/page.tsx‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
1717
const scalar = (key: string) =>
1818
typeof params[key] === 'string' && params[key].length <= 512 ? params[key] : undefined
1919
const ok = params.ok === 'true'
20+
const signInRequired = !ok && params.reason === 'signin_required'
2021
const mode = params.mode === 'managed' ? 'managed' : 'search'
2122
const organizationId = scalar('organizationId')
2223
return (
@@ -25,7 +26,9 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
2526
description={
2627
ok
2728
? 'Your connection is ready. You can return to Sim.'
28-
: 'Authorization did not complete. Return to Sim and try connecting again.'
29+
: signInRequired
30+
? 'Sign in to Sim in your browser, then return to Sim and restart Slack setup.'
31+
: 'Authorization did not complete. Return to Sim and try connecting again.'
2932
}
3033
>
3134
<SlackCompletion
@@ -39,12 +42,14 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
3942
/>
4043
<ChipLink
4144
href={
42-
organizationId
43-
? organizationRoutes(organizationId).settingsSection('search-slack')
44-
: APP_ENTRY_PATH
45+
signInRequired
46+
? '/login'
47+
: organizationId
48+
? organizationRoutes(organizationId).settingsSection('search-slack')
49+
: APP_ENTRY_PATH
4550
}
4651
>
47-
Return to Sim
52+
{signInRequired ? 'Sign in to Sim' : 'Return to Sim'}
4853
</ChipLink>
4954
</DesktopHandoffShell>
5055
)

‎apps/sim/app/credential-groups/slack-complete/slack-completion.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function SlackCompletion({
2323
credentialGroupId,
2424
slackBotCredentialId,
2525
}: SlackCompletionProps) {
26-
const started = useRef(false)
26+
const started = useRef<boolean>(false)
2727
useEffect(() => {
2828
if (started.current || !state) return
2929
started.current = true

‎apps/sim/app/desktop/connect/source-connect-launcher.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface SourceConnectLauncherProps {
1414
}
1515

1616
export function SourceConnectLauncher({ requestId, state, port }: SourceConnectLauncherProps) {
17-
const started = useRef(false)
17+
const started = useRef<boolean>(false)
1818
const [error, setError] = useState<string | null>(null)
1919
useEffect(() => {
2020
if (started.current) return

‎apps/sim/lib/desktop/source-browser.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ export async function finishDesktopSourceBrowser(
180180
return false
181181
sessionStorage.removeItem(STORAGE_KEY)
182182
const url = new URL(buildConnectCompletePath(context.state, context.port), window.location.origin)
183-
if (completion.error) url.searchParams.set('error', 'connection_failed')
183+
if (completion.error)
184+
url.searchParams.set(
185+
'error',
186+
completion.error === 'signin_required' ? 'signin_required' : 'connection_failed'
187+
)
184188
else if (context.github) {
185189
try {
186190
const result = await requestJson(readGitHubSearchSetupContract, { query: context.github })

‎apps/sim/lib/desktop/source-connect.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ export async function connectDesktopSource(
4040
new Error(
4141
result?.error === 'cancelled' || result?.error === 'superseded'
4242
? 'Connection canceled. You can try again.'
43-
: 'Connection did not complete. Try connecting again.'
43+
: result?.error === 'signin_required'
44+
? 'Sign in to Sim in your browser, then try connecting again.'
45+
: 'Connection did not complete. Try connecting again.'
4446
)
4547
)
4648
}

‎apps/sim/scripts/fixtures/desktop-source-connect.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function SourceConnectFixture() {
6565
}
6666

6767
function BrowserLauncher() {
68-
const started = useRef(false)
68+
const started = useRef<boolean>(false)
6969
const [error, setError] = useState('')
7070
useEffect(() => {
7171
if (started.current) return
@@ -99,6 +99,7 @@ const content =
9999
mode='search'
100100
ok={params.get('ok') === 'true'}
101101
state={params.get('state') ?? undefined}
102+
reason={params.get('reason') ?? undefined}
102103
/>
103104
) : (
104105
<SourceConnectFixture />

0 commit comments

Comments
 (0)