@oxyhq/services 16.0.0 → 17.0.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/lib/commonjs/index.js +21 -51
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/client.js +0 -14
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAccountDialog.js +764 -0
- package/lib/commonjs/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/commonjs/ui/components/OxyProvider.js +11 -6
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +10 -21
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +25 -112
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/RequireOxyAuth.js +299 -0
- package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +106 -2
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +19 -30
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +37 -267
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/commonjs/ui/index.js +5 -40
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/accountDialogManager.js +91 -0
- package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/commonjs/ui/navigation/routes.js +0 -2
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +10 -2
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/index.js +18 -15
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/client.js +1 -2
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/OxyAccountDialog.js +761 -0
- package/lib/module/ui/components/OxyAccountDialog.js.map +1 -0
- package/lib/module/ui/components/OxyProvider.js +11 -6
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +10 -21
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +26 -112
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/RequireOxyAuth.js +294 -0
- package/lib/module/ui/components/RequireOxyAuth.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +107 -3
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +1 -1
- package/lib/module/ui/hooks/useAuth.js +19 -30
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +38 -267
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -1
- package/lib/module/ui/index.js +1 -5
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/accountDialogManager.js +81 -0
- package/lib/module/ui/navigation/accountDialogManager.js.map +1 -0
- package/lib/module/ui/navigation/routes.js +0 -2
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/ManageAccountScreen.js +4 -3
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +10 -2
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -11
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -4
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts +35 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +13 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +2 -5
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -11
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -4
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts +35 -0
- package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -23
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +55 -0
- package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts +13 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +13 -152
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +2 -5
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +49 -0
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -0
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +9 -2
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +9 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +21 -26
- package/src/ui/client.ts +0 -4
- package/src/ui/components/OxyAccountDialog.tsx +760 -0
- package/src/ui/components/OxyProvider.tsx +12 -6
- package/src/ui/components/OxySignInButton.tsx +10 -21
- package/src/ui/components/ProfileButton.tsx +23 -126
- package/src/ui/components/RequireOxyAuth.tsx +266 -0
- package/src/ui/context/OxyContext.tsx +146 -7
- package/src/ui/hooks/mutations/useAccountMutations.ts +1 -1
- package/src/ui/hooks/useAuth.ts +19 -31
- package/src/ui/hooks/useSwitchableAccounts.ts +42 -376
- package/src/ui/index.ts +2 -5
- package/src/ui/navigation/accountDialogManager.ts +84 -0
- package/src/ui/navigation/routes.ts +0 -4
- package/src/ui/screens/ManageAccountScreen.tsx +3 -2
- package/src/ui/session/createSessionClient.ts +9 -1
- package/src/ui/types/navigation.ts +9 -0
- package/lib/commonjs/ui/components/AccountMenu.js +0 -598
- package/lib/commonjs/ui/components/AccountMenu.js.map +0 -1
- package/lib/commonjs/ui/components/AccountMenuButton.js +0 -135
- package/lib/commonjs/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +0 -780
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/commonjs/ui/components/AnotherDeviceQR.js +0 -116
- package/lib/commonjs/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +0 -518
- package/lib/commonjs/ui/components/ProfileMenu.js.map +0 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +0 -189
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/commonjs/ui/components/SignInModal.js +0 -600
- package/lib/commonjs/ui/components/SignInModal.js.map +0 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +0 -501
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +0 -170
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +0 -51
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -312
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +0 -109
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/module/ui/components/AccountMenu.js +0 -593
- package/lib/module/ui/components/AccountMenu.js.map +0 -1
- package/lib/module/ui/components/AccountMenuButton.js +0 -130
- package/lib/module/ui/components/AccountMenuButton.js.map +0 -1
- package/lib/module/ui/components/AccountSwitcher.js +0 -774
- package/lib/module/ui/components/AccountSwitcher.js.map +0 -1
- package/lib/module/ui/components/AnotherDeviceQR.js +0 -110
- package/lib/module/ui/components/AnotherDeviceQR.js.map +0 -1
- package/lib/module/ui/components/ProfileMenu.js +0 -514
- package/lib/module/ui/components/ProfileMenu.js.map +0 -1
- package/lib/module/ui/components/SignInAccountChooser.js +0 -183
- package/lib/module/ui/components/SignInAccountChooser.js.map +0 -1
- package/lib/module/ui/components/SignInModal.js +0 -591
- package/lib/module/ui/components/SignInModal.js.map +0 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +0 -495
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +0 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +0 -165
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +0 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +0 -48
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +0 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +0 -306
- package/lib/module/ui/screens/OxyAuthScreen.js.map +0 -1
- package/lib/module/utils/deviceFlowSignIn.js +0 -106
- package/lib/module/utils/deviceFlowSignIn.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/commonjs/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/commonjs/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +0 -47
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts +0 -26
- package/lib/typescript/module/ui/components/AccountMenuButton.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +0 -46
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts +0 -34
- package/lib/typescript/module/ui/components/AnotherDeviceQR.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +0 -49
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +0 -29
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/SignInModal.d.ts +0 -28
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +0 -105
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +0 -31
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts +0 -11
- package/lib/typescript/module/ui/screens/AccountSwitcherScreen.d.ts.map +0 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +0 -22
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +0 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +0 -97
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +0 -1
- package/src/ui/components/AccountMenu.tsx +0 -645
- package/src/ui/components/AccountMenuButton.tsx +0 -126
- package/src/ui/components/AccountSwitcher.tsx +0 -751
- package/src/ui/components/AnotherDeviceQR.tsx +0 -119
- package/src/ui/components/ProfileMenu.tsx +0 -564
- package/src/ui/components/SignInAccountChooser.tsx +0 -162
- package/src/ui/components/SignInModal.tsx +0 -530
- package/src/ui/hooks/useOxyAuthSession.ts +0 -635
- package/src/ui/hooks/usePasswordSignIn.ts +0 -207
- package/src/ui/screens/AccountSwitcherScreen.tsx +0 -43
- package/src/ui/screens/OxyAuthScreen.tsx +0 -324
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +0 -231
- package/src/utils/deviceFlowSignIn.ts +0 -152
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment node
|
|
3
|
-
*
|
|
4
|
-
* Regression coverage for the device-flow ("Sign in with Oxy" QR / "Open Oxy
|
|
5
|
-
* Auth") sign-in orchestration:
|
|
6
|
-
*
|
|
7
|
-
* BUG 1 (native): tapping "Sign In with Oxy" opens `OxyAuthScreen`, which
|
|
8
|
-
* creates a device-flow AuthSession. The user signs in successfully, the API
|
|
9
|
-
* authorizes the session and notifies the client — but the screen then called
|
|
10
|
-
* `switchSession` DIRECTLY without first claiming the bearer with the secret
|
|
11
|
-
* `sessionToken`. The session was authorized server-side but the app never
|
|
12
|
-
* became authenticated ("nothing happens").
|
|
13
|
-
*
|
|
14
|
-
* BUG 2 (session-sync cutover regression): once claiming was fixed,
|
|
15
|
-
* `completeDeviceFlowSignIn` still committed the claimed session through
|
|
16
|
-
* `switchSession`, which after the cutover is an account-SWITCH between
|
|
17
|
-
* accounts already registered on the device and throws `No device account
|
|
18
|
-
* found for session "..."` for a freshly-claimed session. The fix commits the
|
|
19
|
-
* claimed session through `commitSession` (`useOxy().handleWebSession`)
|
|
20
|
-
* instead — the SAME path a fresh password sign-in uses to register the
|
|
21
|
-
* account into the device's server-authoritative session set.
|
|
22
|
-
*
|
|
23
|
-
* These tests pin the order (claim before commit), the fail-fast behaviour on a
|
|
24
|
-
* claim that returns no usable session, propagation of a `commitSession`
|
|
25
|
-
* failure, and (device-first) that the rotating refresh token the claim returns
|
|
26
|
-
* is threaded into `commitSession` so the QR sign-in persists a durable session.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import type { SessionLoginResponse, User } from '@oxyhq/core';
|
|
30
|
-
import {
|
|
31
|
-
completeDeviceFlowSignIn,
|
|
32
|
-
type DeviceFlowClient,
|
|
33
|
-
} from '../deviceFlowSignIn';
|
|
34
|
-
|
|
35
|
-
const SESSION_ID = 'session-id-123';
|
|
36
|
-
const SESSION_TOKEN = 'a'.repeat(32);
|
|
37
|
-
const ACCESS_TOKEN = 'access-token-abc';
|
|
38
|
-
const REFRESH_TOKEN = 'refresh-token-def';
|
|
39
|
-
const USER = {
|
|
40
|
-
id: 'user-1',
|
|
41
|
-
username: 'nate',
|
|
42
|
-
name: { displayName: 'Nate' },
|
|
43
|
-
privacySettings: {},
|
|
44
|
-
} as User;
|
|
45
|
-
// The session carries only the minimal shape (`id`/`username`/`name`/`avatar`)
|
|
46
|
-
// — this is what `commitSession` receives, not the full claimed `User`.
|
|
47
|
-
const MINIMAL_USER = { id: USER.id, username: USER.username, name: USER.name, avatar: undefined };
|
|
48
|
-
|
|
49
|
-
describe('completeDeviceFlowSignIn', () => {
|
|
50
|
-
it('claims the sessionToken BEFORE committing the session', async () => {
|
|
51
|
-
const order: string[] = [];
|
|
52
|
-
|
|
53
|
-
const oxyServices: DeviceFlowClient = {
|
|
54
|
-
claimSessionByToken: jest.fn(async (token: string) => {
|
|
55
|
-
expect(token).toBe(SESSION_TOKEN);
|
|
56
|
-
order.push('claim');
|
|
57
|
-
return {
|
|
58
|
-
accessToken: ACCESS_TOKEN,
|
|
59
|
-
sessionId: SESSION_ID,
|
|
60
|
-
deviceId: 'device-1',
|
|
61
|
-
expiresAt: '2026-01-01T00:00:00.000Z',
|
|
62
|
-
user: USER,
|
|
63
|
-
};
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
const commitSession = jest.fn(async (session: SessionLoginResponse): Promise<void> => {
|
|
67
|
-
expect(session).toEqual({
|
|
68
|
-
sessionId: SESSION_ID,
|
|
69
|
-
deviceId: 'device-1',
|
|
70
|
-
expiresAt: '2026-01-01T00:00:00.000Z',
|
|
71
|
-
user: MINIMAL_USER,
|
|
72
|
-
accessToken: ACCESS_TOKEN,
|
|
73
|
-
});
|
|
74
|
-
order.push('commit');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const user = await completeDeviceFlowSignIn({
|
|
78
|
-
oxyServices,
|
|
79
|
-
sessionId: SESSION_ID,
|
|
80
|
-
sessionToken: SESSION_TOKEN,
|
|
81
|
-
commitSession,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
expect(order).toEqual(['claim', 'commit']);
|
|
85
|
-
expect(oxyServices.claimSessionByToken).toHaveBeenCalledWith(SESSION_TOKEN);
|
|
86
|
-
expect(commitSession).toHaveBeenCalledTimes(1);
|
|
87
|
-
expect(user).toBe(USER);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('threads the claim rotating refreshToken into commitSession (durable QR session)', async () => {
|
|
91
|
-
const oxyServices: DeviceFlowClient = {
|
|
92
|
-
claimSessionByToken: jest.fn(async () => ({
|
|
93
|
-
accessToken: ACCESS_TOKEN,
|
|
94
|
-
refreshToken: REFRESH_TOKEN,
|
|
95
|
-
sessionId: SESSION_ID,
|
|
96
|
-
deviceId: 'device-1',
|
|
97
|
-
expiresAt: '2026-01-01T00:00:00.000Z',
|
|
98
|
-
user: USER,
|
|
99
|
-
})),
|
|
100
|
-
};
|
|
101
|
-
const commitSession = jest.fn(async (): Promise<void> => {});
|
|
102
|
-
|
|
103
|
-
await completeDeviceFlowSignIn({
|
|
104
|
-
oxyServices,
|
|
105
|
-
sessionId: SESSION_ID,
|
|
106
|
-
sessionToken: SESSION_TOKEN,
|
|
107
|
-
commitSession,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// The rotating refresh family is carried into the commit funnel so it
|
|
111
|
-
// persists a durable session that survives a reload without a redirect.
|
|
112
|
-
expect(commitSession).toHaveBeenCalledWith({
|
|
113
|
-
sessionId: SESSION_ID,
|
|
114
|
-
deviceId: 'device-1',
|
|
115
|
-
expiresAt: '2026-01-01T00:00:00.000Z',
|
|
116
|
-
user: MINIMAL_USER,
|
|
117
|
-
accessToken: ACCESS_TOKEN,
|
|
118
|
-
refreshToken: REFRESH_TOKEN,
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('falls back to the delivered sessionId/deviceId/expiresAt when the claim omits them', async () => {
|
|
123
|
-
const oxyServices: DeviceFlowClient = {
|
|
124
|
-
claimSessionByToken: jest.fn(async () => ({
|
|
125
|
-
accessToken: ACCESS_TOKEN,
|
|
126
|
-
user: USER,
|
|
127
|
-
})),
|
|
128
|
-
};
|
|
129
|
-
const commitSession = jest.fn(async (): Promise<void> => {});
|
|
130
|
-
|
|
131
|
-
await completeDeviceFlowSignIn({
|
|
132
|
-
oxyServices,
|
|
133
|
-
sessionId: SESSION_ID,
|
|
134
|
-
sessionToken: SESSION_TOKEN,
|
|
135
|
-
commitSession,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
expect(commitSession).toHaveBeenCalledWith({
|
|
139
|
-
sessionId: SESSION_ID,
|
|
140
|
-
deviceId: '',
|
|
141
|
-
expiresAt: '',
|
|
142
|
-
user: MINIMAL_USER,
|
|
143
|
-
accessToken: ACCESS_TOKEN,
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('does NOT commit the session when the claim fails (the original native regression)', async () => {
|
|
148
|
-
const claimError = new Error('claim failed (401)');
|
|
149
|
-
const oxyServices: DeviceFlowClient = {
|
|
150
|
-
claimSessionByToken: jest.fn(async () => {
|
|
151
|
-
throw claimError;
|
|
152
|
-
}),
|
|
153
|
-
};
|
|
154
|
-
const commitSession = jest.fn(async (): Promise<void> => {});
|
|
155
|
-
|
|
156
|
-
await expect(
|
|
157
|
-
completeDeviceFlowSignIn({
|
|
158
|
-
oxyServices,
|
|
159
|
-
sessionId: SESSION_ID,
|
|
160
|
-
sessionToken: SESSION_TOKEN,
|
|
161
|
-
commitSession,
|
|
162
|
-
}),
|
|
163
|
-
).rejects.toThrow('claim failed (401)');
|
|
164
|
-
|
|
165
|
-
// The bearer was never planted, so we must not attempt the bearer-protected
|
|
166
|
-
// commit — surfacing the failure to the caller instead.
|
|
167
|
-
expect(commitSession).not.toHaveBeenCalled();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('throws when the claim returns no accessToken (the session-sync regression guard)', async () => {
|
|
171
|
-
const oxyServices: DeviceFlowClient = {
|
|
172
|
-
claimSessionByToken: jest.fn(async () => ({ sessionId: SESSION_ID, user: USER })),
|
|
173
|
-
};
|
|
174
|
-
const commitSession = jest.fn(async (): Promise<void> => {});
|
|
175
|
-
|
|
176
|
-
await expect(
|
|
177
|
-
completeDeviceFlowSignIn({
|
|
178
|
-
oxyServices,
|
|
179
|
-
sessionId: SESSION_ID,
|
|
180
|
-
sessionToken: SESSION_TOKEN,
|
|
181
|
-
commitSession,
|
|
182
|
-
}),
|
|
183
|
-
).rejects.toThrow('Device-flow claim did not return a usable session');
|
|
184
|
-
|
|
185
|
-
expect(commitSession).not.toHaveBeenCalled();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('throws when the claim returns no user', async () => {
|
|
189
|
-
const oxyServices: DeviceFlowClient = {
|
|
190
|
-
claimSessionByToken: jest.fn(async () => ({ accessToken: ACCESS_TOKEN, sessionId: SESSION_ID })),
|
|
191
|
-
};
|
|
192
|
-
const commitSession = jest.fn(async (): Promise<void> => {});
|
|
193
|
-
|
|
194
|
-
await expect(
|
|
195
|
-
completeDeviceFlowSignIn({
|
|
196
|
-
oxyServices,
|
|
197
|
-
sessionId: SESSION_ID,
|
|
198
|
-
sessionToken: SESSION_TOKEN,
|
|
199
|
-
commitSession,
|
|
200
|
-
}),
|
|
201
|
-
).rejects.toThrow('Device-flow claim did not return a usable session');
|
|
202
|
-
|
|
203
|
-
expect(commitSession).not.toHaveBeenCalled();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('propagates a commitSession failure after a successful claim', async () => {
|
|
207
|
-
const commitError = new Error('session invalid');
|
|
208
|
-
const oxyServices: DeviceFlowClient = {
|
|
209
|
-
claimSessionByToken: jest.fn(async () => ({
|
|
210
|
-
accessToken: ACCESS_TOKEN,
|
|
211
|
-
sessionId: SESSION_ID,
|
|
212
|
-
user: USER,
|
|
213
|
-
})),
|
|
214
|
-
};
|
|
215
|
-
const commitSession = jest.fn(async (): Promise<void> => {
|
|
216
|
-
throw commitError;
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
await expect(
|
|
220
|
-
completeDeviceFlowSignIn({
|
|
221
|
-
oxyServices,
|
|
222
|
-
sessionId: SESSION_ID,
|
|
223
|
-
sessionToken: SESSION_TOKEN,
|
|
224
|
-
commitSession,
|
|
225
|
-
}),
|
|
226
|
-
).rejects.toThrow('session invalid');
|
|
227
|
-
|
|
228
|
-
expect(oxyServices.claimSessionByToken).toHaveBeenCalledTimes(1);
|
|
229
|
-
expect(commitSession).toHaveBeenCalledTimes(1);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared, pure orchestration for completing the cross-app device-flow sign-in
|
|
3
|
-
* (the QR-code / "Open Oxy Auth" path used on native and web).
|
|
4
|
-
*
|
|
5
|
-
* THE FIRST BUG THIS FIXES (native): once another authenticated device
|
|
6
|
-
* approves the pending AuthSession, the originating client is notified
|
|
7
|
-
* (socket / poll / deep-link) with the authorized `sessionId`. Before any
|
|
8
|
-
* session-management code can use it, the client MUST exchange the secret
|
|
9
|
-
* 128-bit `sessionToken` (held only by this client, generated for THIS flow)
|
|
10
|
-
* for the first access token via `claimSessionByToken` — the device-flow
|
|
11
|
-
* equivalent of OAuth's code-for-token exchange (RFC 8628 §3.4).
|
|
12
|
-
*
|
|
13
|
-
* THE SECOND BUG THIS FIXES (session-sync cutover regression): the
|
|
14
|
-
* freshly-claimed session is NOT yet registered in the device's
|
|
15
|
-
* server-authoritative session set — nothing has run
|
|
16
|
-
* `sessionClient.addCurrentAccount()` for it — so it must NOT be committed
|
|
17
|
-
* through `switchSession`. That path is now an account-SWITCH between
|
|
18
|
-
* accounts already registered on this device (`OxyContext`'s
|
|
19
|
-
* `switchSessionForContext`), and throws `No device account found for
|
|
20
|
-
* session "..."` for anything else, surfacing as "Authorization successful
|
|
21
|
-
* but failed to complete sign in." Instead the claimed session must be
|
|
22
|
-
* committed through the SAME path a fresh password sign-in uses —
|
|
23
|
-
* `useOxy().handleWebSession` (`OxyContext`'s `handleWebSSOSession`) — which
|
|
24
|
-
* registers the account into the device set, persists it durably, and
|
|
25
|
-
* hydrates the full user profile.
|
|
26
|
-
*
|
|
27
|
-
* Skipping the claim leaves the SDK with NO bearer token: the session is
|
|
28
|
-
* authorized server-side but the app never becomes authenticated and the UI
|
|
29
|
-
* sits "Waiting for authorization..." forever. Consolidating the
|
|
30
|
-
* claim->commit sequence here keeps native and web identical and
|
|
31
|
-
* unit-testable.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import {
|
|
35
|
-
KeyManager,
|
|
36
|
-
type MinimalUserData,
|
|
37
|
-
type SessionLoginResponse,
|
|
38
|
-
type User,
|
|
39
|
-
} from '@oxyhq/core';
|
|
40
|
-
|
|
41
|
-
interface DeviceFlowClaimResult {
|
|
42
|
-
accessToken?: string;
|
|
43
|
-
/** Rotating refresh-token family head minted by `/auth/session/claim` on the
|
|
44
|
-
* trusted lane. Not on the core method's typed return, so read defensively;
|
|
45
|
-
* threaded into `commitSession` so the QR sign-in persists a durable session
|
|
46
|
-
* that survives a reload without a redirect. */
|
|
47
|
-
refreshToken?: string;
|
|
48
|
-
sessionId?: string;
|
|
49
|
-
deviceId?: string;
|
|
50
|
-
expiresAt?: string;
|
|
51
|
-
user?: User;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The minimal `OxyServices` surface this orchestration needs. Kept as a
|
|
56
|
-
* structural type (rather than importing the full client) so the helper is
|
|
57
|
-
* trivially unit-testable with a stub and never pulls the RN/Expo runtime into
|
|
58
|
-
* a test bundle.
|
|
59
|
-
*
|
|
60
|
-
*/
|
|
61
|
-
export interface DeviceFlowClient {
|
|
62
|
-
/**
|
|
63
|
-
* Exchange the device-flow `sessionToken` for the first access + refresh
|
|
64
|
-
* token, planting them on the client. Single-use; replay is rejected by the
|
|
65
|
-
* API. No bearer required — the high-entropy `sessionToken` IS the credential.
|
|
66
|
-
*/
|
|
67
|
-
claimSessionByToken: (sessionToken: string) => Promise<DeviceFlowClaimResult | undefined>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface CompleteDeviceFlowSignInOptions {
|
|
71
|
-
/** The OxyServices client (or any object exposing `claimSessionByToken`). */
|
|
72
|
-
oxyServices: DeviceFlowClient;
|
|
73
|
-
/** The authorized device session id, delivered by the socket / poll / link. */
|
|
74
|
-
sessionId: string;
|
|
75
|
-
/**
|
|
76
|
-
* The secret `sessionToken` generated for THIS flow and registered via
|
|
77
|
-
* `POST /auth/session/create`. Required to claim the first access token.
|
|
78
|
-
*/
|
|
79
|
-
sessionToken: string;
|
|
80
|
-
/**
|
|
81
|
-
* `useOxy().handleWebSession` — commits the freshly-claimed session into
|
|
82
|
-
* context state through the SAME path a password sign-in uses:
|
|
83
|
-
* registers the account into the device's server-authoritative session set,
|
|
84
|
-
* persists it durably, and hydrates the full user profile. Runs AFTER the
|
|
85
|
-
* bearer is planted so its bearer-protected calls succeed.
|
|
86
|
-
*
|
|
87
|
-
* The `refreshToken` extra (optional; not on the public `SessionLoginResponse`
|
|
88
|
-
* type) carries the rotating refresh family so the commit funnel persists a
|
|
89
|
-
* durable session.
|
|
90
|
-
*/
|
|
91
|
-
commitSession: (session: SessionLoginResponse & { refreshToken?: string }) => Promise<void>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Complete a device-flow sign-in: claim the first access token with the secret
|
|
96
|
-
* `sessionToken` (planting the bearer), then commit the resulting session via
|
|
97
|
-
* `commitSession` (registers it into the device's server-authoritative session
|
|
98
|
-
* set and hydrates the full user). Returns the authenticated user.
|
|
99
|
-
*
|
|
100
|
-
* Throws if the claim did not return a usable session, or if either the claim
|
|
101
|
-
* or the commit fails; callers surface a retry UI.
|
|
102
|
-
*/
|
|
103
|
-
export async function completeDeviceFlowSignIn({
|
|
104
|
-
oxyServices,
|
|
105
|
-
sessionId,
|
|
106
|
-
sessionToken,
|
|
107
|
-
commitSession,
|
|
108
|
-
}: CompleteDeviceFlowSignInOptions): Promise<User> {
|
|
109
|
-
// 1) Plant the bearer + refresh tokens. The claim response is also persisted
|
|
110
|
-
// to native shared secure storage so a later cold boot has a bearer before
|
|
111
|
-
// it validates stored sessions. On web this is a no-op.
|
|
112
|
-
const claimed = await oxyServices.claimSessionByToken(sessionToken);
|
|
113
|
-
if (claimed?.accessToken) {
|
|
114
|
-
await KeyManager.storeSharedSession(claimed.sessionId || sessionId, claimed.accessToken);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!claimed?.accessToken || !claimed.user) {
|
|
118
|
-
throw new Error('Device-flow claim did not return a usable session');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// `SessionLoginResponse.user` is the minimal session-carried shape (avatar
|
|
122
|
-
// is `string | undefined`); the claim response returns the full `User`
|
|
123
|
-
// (avatar is `string | null | undefined`). Normalize rather than widening
|
|
124
|
-
// `SessionLoginResponse.user` to accept `null`.
|
|
125
|
-
const minimalUser: MinimalUserData = {
|
|
126
|
-
id: claimed.user.id,
|
|
127
|
-
username: claimed.user.username,
|
|
128
|
-
name: claimed.user.name,
|
|
129
|
-
avatar: claimed.user.avatar ?? undefined,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// 2) Bearer is now planted — commit the session through the same path a
|
|
133
|
-
// fresh sign-in uses so it is registered into the device's
|
|
134
|
-
// server-authoritative session set instead of an account switch. Thread
|
|
135
|
-
// the rotating refresh token so the commit funnel persists a durable
|
|
136
|
-
// session that survives a reload.
|
|
137
|
-
await commitSession({
|
|
138
|
-
sessionId: claimed.sessionId || sessionId,
|
|
139
|
-
deviceId: claimed.deviceId ?? '',
|
|
140
|
-
expiresAt: claimed.expiresAt ?? '',
|
|
141
|
-
user: minimalUser,
|
|
142
|
-
accessToken: claimed.accessToken,
|
|
143
|
-
...(claimed.refreshToken ? { refreshToken: claimed.refreshToken } : {}),
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// The commit funnel persisted the rotating refresh family and registered the
|
|
147
|
-
// account into the device set, so a reload restores the session locally (web:
|
|
148
|
-
// stored-tokens / same-apex web-session; native: shared keychain) WITHOUT any
|
|
149
|
-
// IdP `fedcm_session` cookie or top-level establish hop.
|
|
150
|
-
|
|
151
|
-
return claimed.user;
|
|
152
|
-
}
|