mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
fix(cloud-auth): ignore a stale sign-in after a later wait succeeds
A second Sign in click still starts a new loopback wait. Completing that newer wait links the session; finishing the older tab afterwards is cancelled instead of overwriting the linked identity or toasting again.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import type {
|
||||
OrcaCloudCapabilities,
|
||||
OrcaCloudOrgSummary,
|
||||
OrcaProfileCloudSummary
|
||||
} from '../../shared/orca-profiles'
|
||||
|
||||
const { beginOrcaCloudPkceFlowMock, exchangeOrcaCloudAuthCodeMock, safeStorageMock } = vi.hoisted(
|
||||
() => ({
|
||||
beginOrcaCloudPkceFlowMock: vi.fn(),
|
||||
exchangeOrcaCloudAuthCodeMock: vi.fn(),
|
||||
safeStorageMock: {
|
||||
decryptString: vi.fn((value: Buffer) => value.toString('utf-8')),
|
||||
encryptString: vi.fn((value: string) => Buffer.from(value, 'utf-8')),
|
||||
isEncryptionAvailable: vi.fn(() => true)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
let userDataPath = ''
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getPath: () => userDataPath
|
||||
},
|
||||
safeStorage: safeStorageMock
|
||||
}))
|
||||
|
||||
vi.mock('./profile-cloud-pkce', () => ({
|
||||
beginOrcaCloudPkceFlow: beginOrcaCloudPkceFlowMock
|
||||
}))
|
||||
|
||||
vi.mock('./profile-cloud-client', () => ({
|
||||
createOrcaCloudProfile: vi.fn(),
|
||||
exchangeOrcaCloudAuthCode: exchangeOrcaCloudAuthCodeMock,
|
||||
revokeOrcaCloudSession: vi.fn(),
|
||||
selectOrcaCloudOrg: vi.fn()
|
||||
}))
|
||||
|
||||
import { connectCurrentOrcaProfile, getCurrentOrcaProfileAuthStatus } from './profile-cloud-service'
|
||||
|
||||
const earlierCloud: OrcaProfileCloudSummary = {
|
||||
cloudProfileId: 'cloud-profile-1',
|
||||
userId: 'user-1',
|
||||
email: 'nina@example.com',
|
||||
displayName: 'Nina',
|
||||
linkedAt: 10
|
||||
}
|
||||
|
||||
const laterCloud: OrcaProfileCloudSummary = {
|
||||
...earlierCloud,
|
||||
cloudProfileId: 'cloud-profile-2',
|
||||
userId: 'user-2',
|
||||
email: 'ada@example.com'
|
||||
}
|
||||
|
||||
const capabilities: OrcaCloudCapabilities = {
|
||||
flags: { share: true },
|
||||
refreshedAt: 11
|
||||
}
|
||||
|
||||
const organizations: OrcaCloudOrgSummary[] = [{ orgId: 'org-1', name: 'Acme', role: 'Admin' }]
|
||||
|
||||
describe('Orca cloud overlapping connect', () => {
|
||||
beforeEach(() => {
|
||||
userDataPath = mkdtempSync(join(tmpdir(), 'orca-cloud-connect-overlap-'))
|
||||
beginOrcaCloudPkceFlowMock.mockReset()
|
||||
exchangeOrcaCloudAuthCodeMock.mockReset()
|
||||
safeStorageMock.decryptString.mockReset()
|
||||
safeStorageMock.encryptString.mockReset()
|
||||
safeStorageMock.isEncryptionAvailable.mockReset()
|
||||
safeStorageMock.decryptString.mockImplementation((value: Buffer) => value.toString('utf-8'))
|
||||
safeStorageMock.encryptString.mockImplementation((value: string) => Buffer.from(value, 'utf-8'))
|
||||
safeStorageMock.isEncryptionAvailable.mockReturnValue(true)
|
||||
vi.stubEnv('ORCA_CLOUD_API_URL', 'https://orca-cloud.example')
|
||||
vi.stubEnv('ORCA_CLOUD_CLIENT_ID', 'desktop-client')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(userDataPath, { recursive: true, force: true })
|
||||
vi.unstubAllEnvs()
|
||||
})
|
||||
|
||||
it('does not let an earlier sign-in overwrite a later successful connect', async () => {
|
||||
let finishFirst!: (value: {
|
||||
code: string
|
||||
codeVerifier: string
|
||||
nonce: string
|
||||
redirectUri: string
|
||||
state: string
|
||||
}) => void
|
||||
beginOrcaCloudPkceFlowMock
|
||||
.mockReturnValueOnce(
|
||||
new Promise((resolve) => {
|
||||
finishFirst = resolve
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce({
|
||||
code: 'later-code',
|
||||
codeVerifier: 'later-verifier',
|
||||
nonce: 'later-nonce',
|
||||
redirectUri: 'http://127.0.0.1:4101/auth/callback',
|
||||
state: 'later-state'
|
||||
})
|
||||
exchangeOrcaCloudAuthCodeMock.mockImplementation(async (_config, args) => ({
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
expiresAt: Date.now() + 3_600_000,
|
||||
cloud: args.code === 'later-code' ? laterCloud : earlierCloud,
|
||||
organizations,
|
||||
capabilities
|
||||
}))
|
||||
|
||||
const first = connectCurrentOrcaProfile(userDataPath)
|
||||
const later = connectCurrentOrcaProfile(userDataPath)
|
||||
await expect(later).resolves.toMatchObject({ status: 'connected' })
|
||||
expect(getCurrentOrcaProfileAuthStatus(userDataPath).cloud?.email).toBe('ada@example.com')
|
||||
|
||||
finishFirst({
|
||||
code: 'earlier-code',
|
||||
codeVerifier: 'earlier-verifier',
|
||||
nonce: 'earlier-nonce',
|
||||
redirectUri: 'http://127.0.0.1:4100/auth/callback',
|
||||
state: 'earlier-state'
|
||||
})
|
||||
await expect(first).resolves.toMatchObject({ status: 'cancelled' })
|
||||
expect(exchangeOrcaCloudAuthCodeMock).toHaveBeenCalledTimes(1)
|
||||
expect(exchangeOrcaCloudAuthCodeMock).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.objectContaining({ code: 'later-code' })
|
||||
)
|
||||
expect(getCurrentOrcaProfileAuthStatus(userDataPath).cloud?.email).toBe('ada@example.com')
|
||||
})
|
||||
})
|
||||
@@ -36,6 +36,9 @@ import { selectCloudOrgWithMutationFence } from './profile-cloud-org-selection'
|
||||
|
||||
export { refreshCurrentOrcaProfileAuth } from './profile-cloud-capability-refresh'
|
||||
|
||||
let nextCloudConnectAttempt = 0
|
||||
let linkedCloudConnectAttempt = 0
|
||||
|
||||
function isUserCancelledAuthError(message: string): boolean {
|
||||
return message === 'orca_cloud_auth_timeout' || message === 'orca_cloud_auth_denied'
|
||||
}
|
||||
@@ -73,14 +76,28 @@ export async function connectCurrentOrcaProfile(
|
||||
}
|
||||
}
|
||||
|
||||
const attempt = ++nextCloudConnectAttempt
|
||||
try {
|
||||
const code = await beginOrcaCloudPkceFlow(configState.config, active.profile.id)
|
||||
if (attempt < linkedCloudConnectAttempt) {
|
||||
return {
|
||||
status: 'cancelled',
|
||||
auth: getCurrentOrcaProfileAuthStatus(userDataPath)
|
||||
}
|
||||
}
|
||||
const exchange = await exchangeOrcaCloudAuthCode(configState.config, {
|
||||
...code,
|
||||
localProfileId: active.profile.id
|
||||
})
|
||||
if (attempt < linkedCloudConnectAttempt) {
|
||||
return {
|
||||
status: 'cancelled',
|
||||
auth: getCurrentOrcaProfileAuthStatus(userDataPath)
|
||||
}
|
||||
}
|
||||
saveOrcaCloudSessionExchange(active.profile.id, userDataPath, exchange)
|
||||
const list = linkOrcaProfileToCloud(active.profile.id, exchange.cloud, userDataPath)
|
||||
linkedCloudConnectAttempt = attempt
|
||||
return {
|
||||
status: 'connected',
|
||||
auth: getCurrentOrcaProfileAuthStatus(userDataPath),
|
||||
|
||||
@@ -63,7 +63,6 @@ function storeState(): Record<string, unknown> {
|
||||
closeArtifactsPage: mocks.closePage,
|
||||
connectCurrentOrcaProfile: mocks.connect,
|
||||
orcaProfileAuthStatus: mocks.authStatus,
|
||||
|
||||
refreshCurrentOrcaProfileAuth: mocks.refreshAuth,
|
||||
settings: mocks.settings,
|
||||
updateSettings: mocks.updateSettings,
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type {
|
||||
ConnectCurrentOrcaProfileResult,
|
||||
OrcaProfileAuthStatus,
|
||||
OrcaProfileListState
|
||||
} from '../../../../shared/orca-profiles'
|
||||
import { createTestStore } from './store-test-helpers'
|
||||
|
||||
const { toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({
|
||||
toastErrorMock: vi.fn(),
|
||||
toastSuccessMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
error: toastErrorMock,
|
||||
info: vi.fn(),
|
||||
success: toastSuccessMock,
|
||||
warning: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
const listState: OrcaProfileListState = {
|
||||
activeProfileId: 'local-default',
|
||||
profiles: [
|
||||
{
|
||||
id: 'local-default',
|
||||
name: 'Personal',
|
||||
avatar: { kind: 'initials', initials: 'P', color: 'neutral' },
|
||||
kind: 'local',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
lastOpenedAt: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const connectedCloud = {
|
||||
cloudProfileId: 'cloud-profile-1',
|
||||
userId: 'user-1',
|
||||
email: 'nina@example.com',
|
||||
linkedAt: 3
|
||||
}
|
||||
|
||||
const connectedAuthStatus: OrcaProfileAuthStatus = {
|
||||
activeProfileId: 'local-default',
|
||||
configured: true,
|
||||
state: 'connected',
|
||||
persistence: 'encrypted',
|
||||
cloud: connectedCloud,
|
||||
organizations: [{ orgId: 'org-1', name: 'Acme', role: 'Admin' }],
|
||||
capabilities: { flags: { share: true }, refreshedAt: 4 }
|
||||
}
|
||||
|
||||
const orcaProfilesApi = {
|
||||
connectCurrent: vi.fn()
|
||||
}
|
||||
|
||||
describe('orca profile overlapping connect actions', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
toastErrorMock.mockReset()
|
||||
toastSuccessMock.mockReset()
|
||||
vi.stubGlobal('window', {
|
||||
api: { orcaProfiles: orcaProfilesApi }
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the later sign-in and one success toast when both waits complete', async () => {
|
||||
const laterCloud = { ...connectedCloud, userId: 'user-2', email: 'ada@example.com' }
|
||||
const laterAuthStatus: OrcaProfileAuthStatus = {
|
||||
...connectedAuthStatus,
|
||||
cloud: laterCloud
|
||||
}
|
||||
const earlierConnected: ConnectCurrentOrcaProfileResult = {
|
||||
status: 'connected',
|
||||
auth: connectedAuthStatus,
|
||||
activeProfileId: 'local-default',
|
||||
profiles: [{ ...listState.profiles[0], kind: 'cloud-linked', cloud: connectedCloud }]
|
||||
}
|
||||
const laterConnected: ConnectCurrentOrcaProfileResult = {
|
||||
status: 'connected',
|
||||
auth: laterAuthStatus,
|
||||
activeProfileId: 'local-default',
|
||||
profiles: [{ ...listState.profiles[0], kind: 'cloud-linked', cloud: laterCloud }]
|
||||
}
|
||||
let finishFirst!: (value: ConnectCurrentOrcaProfileResult) => void
|
||||
orcaProfilesApi.connectCurrent
|
||||
.mockReturnValueOnce(
|
||||
new Promise<ConnectCurrentOrcaProfileResult>((resolve) => {
|
||||
finishFirst = resolve
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(laterConnected)
|
||||
const store = createTestStore()
|
||||
|
||||
const first = store.getState().connectCurrentOrcaProfile()
|
||||
const second = store.getState().connectCurrentOrcaProfile()
|
||||
await expect(second).resolves.toEqual(laterConnected)
|
||||
finishFirst(earlierConnected)
|
||||
await expect(first).resolves.toEqual(earlierConnected)
|
||||
expect(toastSuccessMock).toHaveBeenCalledOnce()
|
||||
expect(toastErrorMock).not.toHaveBeenCalled()
|
||||
expect(store.getState().orcaProfileAuthStatus).toEqual(laterAuthStatus)
|
||||
expect(store.getState().orcaProfiles).toEqual(laterConnected.profiles)
|
||||
})
|
||||
})
|
||||
@@ -29,186 +29,209 @@ export const createOrcaProfilesAuthActions: StateCreator<
|
||||
[],
|
||||
[],
|
||||
OrcaProfilesAuthActions
|
||||
> = (set, get) => ({
|
||||
createCloudLinkedOrcaProfile: async (args) => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.createCloudLinked(args)
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'created'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'created') {
|
||||
toast.success(
|
||||
translate('auto.store.slices.orca.profiles.319d7cf39b', 'Cloud profile created')
|
||||
)
|
||||
} else if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
> = (set, get) => {
|
||||
let nextConnectAttempt = 0
|
||||
let appliedConnectAttempt = 0
|
||||
|
||||
return {
|
||||
createCloudLinkedOrcaProfile: async (args) => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.createCloudLinked(args)
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'created'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'created') {
|
||||
toast.success(
|
||||
translate('auto.store.slices.orca.profiles.319d7cf39b', 'Cloud profile created')
|
||||
)
|
||||
} else if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.store.slices.orca.profiles.f0c9e11a6d',
|
||||
'Failed to create cloud profile'
|
||||
),
|
||||
{ description: result.error }
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to create Orca cloud profile:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.f0c9e11a6d', 'Failed to create cloud profile'),
|
||||
{ description: result.error }
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to create Orca cloud profile:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.f0c9e11a6d', 'Failed to create cloud profile'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
connectCurrentOrcaProfile: async () => {
|
||||
try {
|
||||
// Why: a pending browser callback must not block retry. Another click
|
||||
// starts a second PKCE wait; the earlier tab can still complete.
|
||||
const alreadyConnected = get().orcaProfileAuthStatus?.state === 'connected'
|
||||
const result = await window.api.orcaProfiles.connectCurrent()
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'connected'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'unconfigured') {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.store.slices.orca.profiles.8b8fa73174',
|
||||
'Orca Cloud sign-in is not configured'
|
||||
),
|
||||
{
|
||||
description: result.auth.setupMessage
|
||||
}
|
||||
)
|
||||
} else if (
|
||||
result.status === 'failed' &&
|
||||
!alreadyConnected &&
|
||||
result.auth.state !== 'connected'
|
||||
) {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
|
||||
{ description: result.error }
|
||||
)
|
||||
} else if (result.status === 'connected' && !alreadyConnected) {
|
||||
toast.success(translate('auto.store.slices.orca.profiles.9fcb07a796', 'Profile connected'))
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to connect Orca profile:', err)
|
||||
if (get().orcaProfileAuthStatus?.state !== 'connected') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
refreshCurrentOrcaProfileAuth: async () => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.refreshAuth()
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'refreshed'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
connectCurrentOrcaProfile: async () => {
|
||||
const attempt = ++nextConnectAttempt
|
||||
try {
|
||||
// Why: a pending browser callback must not block retry. Another click
|
||||
// starts a second PKCE wait; an older wait is ignored after a newer
|
||||
// one has already linked.
|
||||
const result = await window.api.orcaProfiles.connectCurrent()
|
||||
if (attempt < appliedConnectAttempt) {
|
||||
return result
|
||||
}
|
||||
const alreadyConnected = get().orcaProfileAuthStatus?.state === 'connected'
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'connected'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'connected') {
|
||||
appliedConnectAttempt = attempt
|
||||
if (!alreadyConnected) {
|
||||
toast.success(
|
||||
translate('auto.store.slices.orca.profiles.9fcb07a796', 'Profile connected')
|
||||
)
|
||||
}
|
||||
} else if (result.status === 'unconfigured') {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.store.slices.orca.profiles.8b8fa73174',
|
||||
'Orca Cloud sign-in is not configured'
|
||||
),
|
||||
{
|
||||
description: result.auth.setupMessage
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
)
|
||||
} else if (result.status === 'failed' && !alreadyConnected) {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
|
||||
{ description: result.error }
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to connect Orca profile:', err)
|
||||
if (
|
||||
attempt >= appliedConnectAttempt &&
|
||||
get().orcaProfileAuthStatus?.state !== 'connected'
|
||||
) {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
refreshCurrentOrcaProfileAuth: async () => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.refreshAuth()
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'refreshed'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.store.slices.orca.profiles.2f6c78a039',
|
||||
'Failed to refresh profile auth'
|
||||
),
|
||||
{ description: result.error }
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to refresh Orca profile auth:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.2f6c78a039', 'Failed to refresh profile auth'),
|
||||
{ description: result.error }
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to refresh Orca profile auth:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.2f6c78a039', 'Failed to refresh profile auth'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
signOutCurrentOrcaProfile: async () => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.signOutCurrent()
|
||||
set({
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles,
|
||||
orcaProfileAuthStatus: result.auth
|
||||
})
|
||||
toast.success(
|
||||
translate('auto.store.slices.orca.profiles.a37b5e6d37', 'Signed out of profile')
|
||||
)
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to sign out of Orca profile:', err)
|
||||
toast.error(translate('auto.store.slices.orca.profiles.83600521e7', 'Failed to sign out'), {
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
selectOrcaProfileOrg: async (orgId) => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.selectOrg({ orgId })
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'selected'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
signOutCurrentOrcaProfile: async () => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.signOutCurrent()
|
||||
set({
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles,
|
||||
orcaProfileAuthStatus: result.auth
|
||||
})
|
||||
toast.success(
|
||||
translate('auto.store.slices.orca.profiles.a37b5e6d37', 'Signed out of profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to sign out of Orca profile:', err)
|
||||
toast.error(translate('auto.store.slices.orca.profiles.83600521e7', 'Failed to sign out'), {
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
selectOrcaProfileOrg: async (orgId) => {
|
||||
try {
|
||||
const result = await window.api.orcaProfiles.selectOrg({ orgId })
|
||||
set({
|
||||
orcaProfileAuthStatus: result.auth,
|
||||
...(result.status === 'selected'
|
||||
? {
|
||||
activeOrcaProfileId: result.activeProfileId,
|
||||
orcaProfiles: result.profiles
|
||||
}
|
||||
: {})
|
||||
})
|
||||
if (result.status === 'reconnect-required') {
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.d6e764e7db', 'Reconnect this profile')
|
||||
)
|
||||
} else if (result.status === 'failed') {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.store.slices.orca.profiles.76deec8f58',
|
||||
'Failed to switch organization'
|
||||
),
|
||||
{ description: result.error }
|
||||
)
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to switch Orca profile org:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.76deec8f58', 'Failed to switch organization'),
|
||||
{ description: result.error }
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Failed to switch Orca profile org:', err)
|
||||
toast.error(
|
||||
translate('auto.store.slices.orca.profiles.76deec8f58', 'Failed to switch organization'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user