@oxyhq/services 14.0.1 → 15.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 +0 -17
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +189 -0
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/commonjs/ui/components/SignInModal.js +350 -131
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +340 -1547
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +61 -64
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +14 -33
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +170 -0
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js +2 -5
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -2
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +1 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +93 -68
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileScreen.js +187 -0
- package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +231 -95
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +15 -2
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/linkFormat.js +38 -0
- package/lib/commonjs/ui/screens/linkFormat.js.map +1 -0
- package/lib/commonjs/ui/session/authStore.js +146 -0
- package/lib/commonjs/ui/session/authStore.js.map +1 -0
- package/lib/commonjs/ui/session/createSessionClient.js +25 -10
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/index.js +13 -7
- package/lib/commonjs/ui/session/index.js.map +1 -1
- package/lib/commonjs/ui/session/tokenTransport.js +19 -31
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/isWebBrowser.js +13 -0
- package/lib/commonjs/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/commonjs/utils/deviceFlowSignIn.js +13 -16
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +0 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/SignInAccountChooser.js +183 -0
- package/lib/module/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/module/ui/components/SignInModal.js +352 -134
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +342 -1551
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +60 -63
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +14 -33
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +165 -0
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/module/ui/hooks/useProfileEditing.js +2 -5
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +1 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +91 -66
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileScreen.js +182 -0
- package/lib/module/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/module/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +233 -97
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +15 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/linkFormat.js +31 -0
- package/lib/module/ui/screens/linkFormat.js.map +1 -0
- package/lib/module/ui/session/authStore.js +143 -0
- package/lib/module/ui/session/authStore.js.map +1 -0
- package/lib/module/ui/session/createSessionClient.js +25 -10
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/index.js +9 -9
- package/lib/module/ui/session/index.js.map +1 -1
- package/lib/module/ui/session/tokenTransport.js +20 -33
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/isWebBrowser.js +11 -0
- package/lib/module/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/module/utils/deviceFlowSignIn.js +14 -17
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +0 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
- 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/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- 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/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -0
- 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/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +20 -9
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/index.d.ts +9 -9
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +0 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
- 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/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- 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/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -0
- 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/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +20 -9
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/index.d.ts +9 -9
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +0 -4
- package/src/ui/components/SignInAccountChooser.tsx +162 -0
- package/src/ui/components/SignInModal.tsx +309 -139
- package/src/ui/context/OxyContext.tsx +532 -1785
- package/src/ui/context/hooks/useAuthOperations.ts +65 -76
- package/src/ui/hooks/useAuth.ts +14 -35
- package/src/ui/hooks/usePasswordSignIn.ts +207 -0
- package/src/ui/hooks/useProfileEditing.ts +2 -8
- package/src/ui/hooks/useSessionManagement.ts +1 -1
- package/src/ui/navigation/routes.ts +2 -0
- package/src/ui/screens/EditProfileFieldScreen.tsx +102 -66
- package/src/ui/screens/EditProfileScreen.tsx +155 -0
- package/src/ui/screens/ManageAccountScreen.tsx +3 -7
- package/src/ui/screens/OxyAuthScreen.tsx +259 -112
- package/src/ui/screens/ProfileScreen.tsx +15 -2
- package/src/ui/screens/__tests__/linkFormat.test.ts +73 -0
- package/src/ui/screens/linkFormat.ts +37 -0
- package/src/ui/session/__tests__/createSessionClient.test.ts +49 -6
- package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
- package/src/ui/session/authStore.ts +164 -0
- package/src/ui/session/createSessionClient.ts +32 -11
- package/src/ui/session/index.ts +10 -9
- package/src/ui/session/tokenTransport.ts +26 -36
- package/src/ui/utils/isWebBrowser.ts +8 -0
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +50 -131
- package/src/utils/deviceFlowSignIn.ts +19 -26
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +0 -243
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +0 -56
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +0 -28
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/commonjs/ui/session/projectSessionState.js +0 -86
- package/lib/commonjs/ui/session/projectSessionState.js.map +0 -1
- package/lib/commonjs/ui/session/sessionClientHost.js +0 -30
- package/lib/commonjs/ui/session/sessionClientHost.js.map +0 -1
- package/lib/commonjs/ui/utils/activeAuthuser.js +0 -142
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/commonjs/utils/crossApex.js +0 -74
- package/lib/commonjs/utils/crossApex.js.map +0 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +0 -238
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/module/ui/context/silentSessionRestore.js +0 -53
- package/lib/module/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/module/ui/hooks/useWebSSO.js +0 -24
- package/lib/module/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/module/ui/session/projectSessionState.js +0 -79
- package/lib/module/ui/session/projectSessionState.js.map +0 -1
- package/lib/module/ui/session/sessionClientHost.js +0 -26
- package/lib/module/ui/session/sessionClientHost.js.map +0 -1
- package/lib/module/ui/utils/activeAuthuser.js +0 -134
- package/lib/module/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/module/utils/crossApex.js +0 -69
- package/lib/module/utils/crossApex.js.map +0 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/crossApex.d.ts +0 -55
- package/lib/typescript/commonjs/utils/crossApex.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/module/utils/crossApex.d.ts +0 -55
- package/lib/typescript/module/utils/crossApex.d.ts.map +0 -1
- package/src/ui/context/inSessionTokenRefresh.ts +0 -264
- package/src/ui/context/silentSessionRestore.ts +0 -53
- package/src/ui/hooks/useWebSSO.ts +0 -23
- package/src/ui/session/__tests__/projectSessionState.test.ts +0 -114
- package/src/ui/session/__tests__/sessionClientHost.test.ts +0 -43
- package/src/ui/session/projectSessionState.ts +0 -85
- package/src/ui/session/sessionClientHost.ts +0 -27
- package/src/ui/utils/activeAuthuser.ts +0 -142
- package/src/utils/crossApex.ts +0 -75
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type { DeviceSessionState } from '@oxyhq/contracts';
|
|
2
|
-
import { logger } from '@oxyhq/core';
|
|
2
|
+
import { logger, createMemoryAuthStateStore, refreshPersistedSession } from '@oxyhq/core';
|
|
3
3
|
import { createTokenTransport } from '../tokenTransport';
|
|
4
|
-
import { isWebBrowser } from '../../hooks/useWebSSO';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// The device-first transport mints a fallback token by rotating the persisted
|
|
6
|
+
// refresh family via `refreshPersistedSession` (the ONE unified refresh path) —
|
|
7
|
+
// there is no `silentSignIn`/`signInWithSharedIdentity` arm anymore. Mock that
|
|
8
|
+
// one core function so the transport's own coalescing / logging contract can be
|
|
9
|
+
// exercised in isolation; keep the real `logger` + memory store.
|
|
10
|
+
jest.mock('@oxyhq/core', () => {
|
|
11
|
+
const actual = jest.requireActual('@oxyhq/core');
|
|
12
|
+
return { __esModule: true, ...actual, refreshPersistedSession: jest.fn() };
|
|
13
|
+
});
|
|
9
14
|
|
|
10
|
-
const
|
|
15
|
+
const mockedRefresh = refreshPersistedSession as jest.MockedFunction<typeof refreshPersistedSession>;
|
|
11
16
|
|
|
12
17
|
function fakeOxy(accessToken: string | null) {
|
|
13
18
|
return {
|
|
14
19
|
getAccessToken: jest.fn().mockReturnValue(accessToken),
|
|
15
|
-
silentSignIn: jest.fn().mockResolvedValue(null),
|
|
16
|
-
signInWithSharedIdentity: jest.fn().mockResolvedValue(null),
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -28,51 +31,39 @@ const state: DeviceSessionState = {
|
|
|
28
31
|
describe('createTokenTransport', () => {
|
|
29
32
|
beforeEach(() => {
|
|
30
33
|
jest.clearAllMocks();
|
|
34
|
+
mockedRefresh.mockResolvedValue('minted-token');
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
test('no-ops when a token is already present', async () => {
|
|
34
38
|
const oxy = fakeOxy('tok');
|
|
35
|
-
const transport = createTokenTransport(oxy as never);
|
|
36
|
-
|
|
37
|
-
await transport.ensureActiveToken(state);
|
|
38
|
-
|
|
39
|
-
expect(oxy.silentSignIn).not.toHaveBeenCalled();
|
|
40
|
-
expect(oxy.signInWithSharedIdentity).not.toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('mints via silentSignIn on web when no token is present', async () => {
|
|
44
|
-
mockedIsWebBrowser.mockReturnValue(true);
|
|
45
|
-
const oxy = fakeOxy(null);
|
|
46
|
-
const transport = createTokenTransport(oxy as never);
|
|
39
|
+
const transport = createTokenTransport(oxy as never, createMemoryAuthStateStore());
|
|
47
40
|
|
|
48
41
|
await transport.ensureActiveToken(state);
|
|
49
42
|
|
|
50
|
-
expect(
|
|
51
|
-
expect(oxy.signInWithSharedIdentity).not.toHaveBeenCalled();
|
|
43
|
+
expect(mockedRefresh).not.toHaveBeenCalled();
|
|
52
44
|
});
|
|
53
45
|
|
|
54
|
-
test('mints via
|
|
55
|
-
mockedIsWebBrowser.mockReturnValue(false);
|
|
46
|
+
test('mints via refreshPersistedSession when no token is present', async () => {
|
|
56
47
|
const oxy = fakeOxy(null);
|
|
57
|
-
const
|
|
48
|
+
const store = createMemoryAuthStateStore();
|
|
49
|
+
const transport = createTokenTransport(oxy as never, store);
|
|
58
50
|
|
|
59
51
|
await transport.ensureActiveToken(state);
|
|
60
52
|
|
|
61
|
-
expect(
|
|
62
|
-
expect(
|
|
53
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(1);
|
|
54
|
+
expect(mockedRefresh).toHaveBeenCalledWith({ oxy, store });
|
|
63
55
|
});
|
|
64
56
|
|
|
65
|
-
test('resolves (never rejects) and logs a warning when the
|
|
66
|
-
mockedIsWebBrowser.mockReturnValue(true);
|
|
57
|
+
test('resolves (never rejects) and logs a warning when the refresh throws', async () => {
|
|
67
58
|
const oxy = fakeOxy(null);
|
|
68
|
-
|
|
69
|
-
const transport = createTokenTransport(oxy as never);
|
|
59
|
+
mockedRefresh.mockRejectedValue(new Error('mint boom'));
|
|
60
|
+
const transport = createTokenTransport(oxy as never, createMemoryAuthStateStore());
|
|
70
61
|
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
71
62
|
|
|
72
63
|
await expect(transport.ensureActiveToken(state)).resolves.toBeUndefined();
|
|
73
64
|
|
|
74
65
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
75
|
-
'ensureActiveToken:
|
|
66
|
+
'ensureActiveToken: refresh failed',
|
|
76
67
|
{ component: 'TokenTransport' },
|
|
77
68
|
expect.any(Error),
|
|
78
69
|
);
|
|
@@ -80,52 +71,49 @@ describe('createTokenTransport', () => {
|
|
|
80
71
|
warnSpy.mockRestore();
|
|
81
72
|
});
|
|
82
73
|
|
|
83
|
-
test('resolves cleanly when the
|
|
84
|
-
mockedIsWebBrowser.mockReturnValue(true);
|
|
74
|
+
test('resolves cleanly when the refresh produces no session', async () => {
|
|
85
75
|
const oxy = fakeOxy(null);
|
|
86
|
-
|
|
76
|
+
mockedRefresh.mockResolvedValue(null);
|
|
77
|
+
const transport = createTokenTransport(oxy as never, createMemoryAuthStateStore());
|
|
87
78
|
|
|
88
79
|
await expect(transport.ensureActiveToken(state)).resolves.toBeUndefined();
|
|
89
|
-
expect(
|
|
80
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(1);
|
|
90
81
|
});
|
|
91
82
|
|
|
92
83
|
test('coalesces concurrent calls into a single mint', async () => {
|
|
93
|
-
mockedIsWebBrowser.mockReturnValue(true);
|
|
94
84
|
const oxy = fakeOxy(null);
|
|
95
|
-
let resolveMint: ((value: null) => void) | null = null;
|
|
96
|
-
|
|
85
|
+
let resolveMint: ((value: string | null) => void) | null = null;
|
|
86
|
+
mockedRefresh.mockImplementation(
|
|
97
87
|
() =>
|
|
98
|
-
new Promise((resolve) => {
|
|
88
|
+
new Promise<string | null>((resolve) => {
|
|
99
89
|
resolveMint = resolve;
|
|
100
90
|
}),
|
|
101
91
|
);
|
|
102
|
-
const transport = createTokenTransport(oxy as never);
|
|
92
|
+
const transport = createTokenTransport(oxy as never, createMemoryAuthStateStore());
|
|
103
93
|
|
|
104
94
|
const first = transport.ensureActiveToken(state);
|
|
105
95
|
const second = transport.ensureActiveToken(state);
|
|
106
96
|
|
|
107
|
-
expect(
|
|
97
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(1);
|
|
108
98
|
|
|
109
99
|
resolveMint?.(null);
|
|
110
100
|
await Promise.all([first, second]);
|
|
111
101
|
|
|
112
|
-
expect(
|
|
102
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(1);
|
|
113
103
|
|
|
114
|
-
// A later call after the in-flight mint settled starts a fresh mint
|
|
115
|
-
// (the mock reassigns `resolveMint` on the second silentSignIn call).
|
|
104
|
+
// A later call after the in-flight mint settled starts a fresh mint.
|
|
116
105
|
const third = transport.ensureActiveToken(state);
|
|
117
|
-
expect(
|
|
106
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(2);
|
|
118
107
|
resolveMint?.(null);
|
|
119
108
|
await third;
|
|
120
109
|
});
|
|
121
110
|
|
|
122
111
|
test('treats a throwing getAccessToken as no-token and still mints', async () => {
|
|
123
|
-
mockedIsWebBrowser.mockReturnValue(true);
|
|
124
112
|
const oxy = fakeOxy(null);
|
|
125
113
|
oxy.getAccessToken.mockImplementation(() => {
|
|
126
114
|
throw new Error('storage unavailable');
|
|
127
115
|
});
|
|
128
|
-
const transport = createTokenTransport(oxy as never);
|
|
116
|
+
const transport = createTokenTransport(oxy as never, createMemoryAuthStateStore());
|
|
129
117
|
const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);
|
|
130
118
|
|
|
131
119
|
await expect(transport.ensureActiveToken(state)).resolves.toBeUndefined();
|
|
@@ -135,7 +123,7 @@ describe('createTokenTransport', () => {
|
|
|
135
123
|
{ component: 'TokenTransport' },
|
|
136
124
|
expect.any(Error),
|
|
137
125
|
);
|
|
138
|
-
expect(
|
|
126
|
+
expect(mockedRefresh).toHaveBeenCalledTimes(1);
|
|
139
127
|
|
|
140
128
|
warnSpy.mockRestore();
|
|
141
129
|
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform `AuthStateStore` for `@oxyhq/services`.
|
|
3
|
+
*
|
|
4
|
+
* The device-first session model persists the rotating refresh-token family
|
|
5
|
+
* head per origin (web) / per device (native). `@oxyhq/core` owns the store
|
|
6
|
+
* shape + logic (`createWebAuthStateStore` / `createNativeAuthStateStore`); this
|
|
7
|
+
* module only supplies the platform storage seam and wires the native device
|
|
8
|
+
* token to the SHARED keychain so every native Oxy app joins one DeviceSession:
|
|
9
|
+
*
|
|
10
|
+
* - web → `createWebAuthStateStore()` (localStorage, in-memory fallback).
|
|
11
|
+
* - native → `createNativeAuthStateStore(secureKV)` for the SESSION blob
|
|
12
|
+
* (refresh token) over `expo-secure-store` (AsyncStorage fallback), with the
|
|
13
|
+
* long-lived DEVICE token delegated to `KeyManager`'s shared keychain
|
|
14
|
+
* (`group.so.oxy.shared`) instead of per-app storage — so an in-app login on
|
|
15
|
+
* any native Oxy app attributes to the SAME server-side DeviceSession.
|
|
16
|
+
*
|
|
17
|
+
* `expo-secure-store` is loaded via a runtime-computed dynamic import (the same
|
|
18
|
+
* optional-native-module pattern `OxyProvider` uses for netinfo / keyboard
|
|
19
|
+
* controller), so the web bundle never pulls it and a device without it falls
|
|
20
|
+
* back to AsyncStorage rather than crashing.
|
|
21
|
+
*/
|
|
22
|
+
import {
|
|
23
|
+
KeyManager,
|
|
24
|
+
createWebAuthStateStore,
|
|
25
|
+
createNativeAuthStateStore,
|
|
26
|
+
type AuthStateStore,
|
|
27
|
+
type NativeKeyValueStorage,
|
|
28
|
+
logger,
|
|
29
|
+
} from '@oxyhq/core';
|
|
30
|
+
import { createPlatformStorage, isReactNative } from '../utils/storageHelpers';
|
|
31
|
+
|
|
32
|
+
// Variable indirection so Metro's static analyzer never traces expo-secure-store
|
|
33
|
+
// into the web bundle; the module is native-only and optional.
|
|
34
|
+
const SECURE_STORE_MODULE = 'expo-secure-store';
|
|
35
|
+
|
|
36
|
+
interface SecureStoreLike {
|
|
37
|
+
getItemAsync(key: string): Promise<string | null>;
|
|
38
|
+
setItemAsync(key: string, value: string): Promise<void>;
|
|
39
|
+
deleteItemAsync(key: string): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let secureStorePromise: Promise<SecureStoreLike | null> | null = null;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the `expo-secure-store` module once (memoised), or `null` when it is
|
|
46
|
+
* unavailable — callers then fall back to AsyncStorage. Never throws.
|
|
47
|
+
*/
|
|
48
|
+
function loadSecureStore(): Promise<SecureStoreLike | null> {
|
|
49
|
+
if (!secureStorePromise) {
|
|
50
|
+
const moduleName = SECURE_STORE_MODULE;
|
|
51
|
+
secureStorePromise = import(moduleName)
|
|
52
|
+
.then((mod: Partial<SecureStoreLike>) => {
|
|
53
|
+
if (
|
|
54
|
+
typeof mod.getItemAsync === 'function' &&
|
|
55
|
+
typeof mod.setItemAsync === 'function' &&
|
|
56
|
+
typeof mod.deleteItemAsync === 'function'
|
|
57
|
+
) {
|
|
58
|
+
return {
|
|
59
|
+
getItemAsync: mod.getItemAsync.bind(mod),
|
|
60
|
+
setItemAsync: mod.setItemAsync.bind(mod),
|
|
61
|
+
deleteItemAsync: mod.deleteItemAsync.bind(mod),
|
|
62
|
+
} satisfies SecureStoreLike;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
})
|
|
66
|
+
.catch(() => null);
|
|
67
|
+
}
|
|
68
|
+
return secureStorePromise;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A native `NativeKeyValueStorage` that prefers `expo-secure-store` (encrypted
|
|
73
|
+
* at rest) and falls back to AsyncStorage when SecureStore is not installed.
|
|
74
|
+
* The SecureStore module is resolved lazily on first access so construction
|
|
75
|
+
* stays synchronous.
|
|
76
|
+
*/
|
|
77
|
+
function createNativeSecureKeyValueStorage(): NativeKeyValueStorage {
|
|
78
|
+
let asyncStorage: Awaited<ReturnType<typeof createPlatformStorage>> | null = null;
|
|
79
|
+
const getAsyncStorage = async () => {
|
|
80
|
+
if (!asyncStorage) {
|
|
81
|
+
asyncStorage = await createPlatformStorage();
|
|
82
|
+
}
|
|
83
|
+
return asyncStorage;
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
getItem: async (key) => {
|
|
87
|
+
const secure = await loadSecureStore();
|
|
88
|
+
if (secure) {
|
|
89
|
+
return secure.getItemAsync(key);
|
|
90
|
+
}
|
|
91
|
+
return (await getAsyncStorage()).getItem(key);
|
|
92
|
+
},
|
|
93
|
+
setItem: async (key, value) => {
|
|
94
|
+
const secure = await loadSecureStore();
|
|
95
|
+
if (secure) {
|
|
96
|
+
await secure.setItemAsync(key, value);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await (await getAsyncStorage()).setItem(key, value);
|
|
100
|
+
},
|
|
101
|
+
removeItem: async (key) => {
|
|
102
|
+
const secure = await loadSecureStore();
|
|
103
|
+
if (secure) {
|
|
104
|
+
await secure.deleteItemAsync(key);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await (await getAsyncStorage()).removeItem(key);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build the platform {@link AuthStateStore} for this runtime.
|
|
114
|
+
*
|
|
115
|
+
* Native additionally routes the device token through the shared keychain so
|
|
116
|
+
* every native Oxy app shares one server-side DeviceSession; the session blob
|
|
117
|
+
* (refresh token) stays per-app in SecureStore. Best-effort keychain access —
|
|
118
|
+
* a locked/failed shared-keychain read degrades to "no device token" rather
|
|
119
|
+
* than throwing out of cold boot / refresh.
|
|
120
|
+
*/
|
|
121
|
+
export function createPlatformAuthStateStore(): AuthStateStore {
|
|
122
|
+
if (!isReactNative()) {
|
|
123
|
+
return createWebAuthStateStore();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const base = createNativeAuthStateStore(createNativeSecureKeyValueStorage());
|
|
127
|
+
return {
|
|
128
|
+
...base,
|
|
129
|
+
loadDeviceToken: async () => {
|
|
130
|
+
try {
|
|
131
|
+
return await KeyManager.getSharedDeviceToken();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.debug(
|
|
134
|
+
'Shared device-token read failed (treating as none)',
|
|
135
|
+
{ component: 'authStore', method: 'loadDeviceToken' },
|
|
136
|
+
error,
|
|
137
|
+
);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
saveDeviceToken: async (token) => {
|
|
142
|
+
try {
|
|
143
|
+
await KeyManager.setSharedDeviceToken(token);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.debug(
|
|
146
|
+
'Shared device-token write failed (non-fatal)',
|
|
147
|
+
{ component: 'authStore', method: 'saveDeviceToken' },
|
|
148
|
+
error,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
clearDeviceToken: async () => {
|
|
153
|
+
try {
|
|
154
|
+
await KeyManager.clearSharedDeviceToken();
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.debug(
|
|
157
|
+
'Shared device-token clear failed (non-fatal)',
|
|
158
|
+
{ component: 'authStore', method: 'clearDeviceToken' },
|
|
159
|
+
error,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -1,21 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import {
|
|
3
|
+
SessionClient,
|
|
4
|
+
createSessionClientHost,
|
|
5
|
+
type AuthStateStore,
|
|
6
|
+
type OxyServices,
|
|
7
|
+
} from '@oxyhq/core';
|
|
4
8
|
import { createTokenTransport } from './tokenTransport';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Wire a `SessionClient` for `@oxyhq/services`.
|
|
12
|
+
*
|
|
13
|
+
* The platform-agnostic parts (host adapter, client, projection helpers) live
|
|
14
|
+
* ONCE in `@oxyhq/core`; this thin factory only injects the pieces specific to
|
|
15
|
+
* the RN/Expo SDK:
|
|
16
|
+
*
|
|
17
|
+
* - `socket.io-client`'s `io`, STATICALLY imported (socket.io-client is a real
|
|
18
|
+
* dependency of `@oxyhq/services`) so realtime session sync never relies on
|
|
19
|
+
* core's lazy dynamic import of a bare specifier — bundler-fragile in
|
|
20
|
+
* Metro/Expo-web against the published core dist.
|
|
21
|
+
* - the device-first {@link createTokenTransport}, which mints a fallback token
|
|
22
|
+
* by rotating the persisted refresh family (`store`).
|
|
23
|
+
*
|
|
24
|
+
* `onUnauthenticated` fires when an applied device state has zero accounts (a
|
|
25
|
+
* device signout-all): the provider clears the persisted store + local state so
|
|
26
|
+
* a reload does not try to restore a dead session. The host is returned
|
|
27
|
+
* alongside the client so the caller can call `host.setCurrentAccountId(...)`
|
|
28
|
+
* as the active account changes.
|
|
12
29
|
*/
|
|
13
|
-
export function createSessionClient(
|
|
30
|
+
export function createSessionClient(
|
|
31
|
+
oxyServices: OxyServices,
|
|
32
|
+
store: AuthStateStore,
|
|
33
|
+
onUnauthenticated?: () => void,
|
|
34
|
+
): {
|
|
14
35
|
client: SessionClient;
|
|
15
36
|
host: ReturnType<typeof createSessionClientHost>;
|
|
16
37
|
} {
|
|
17
38
|
const host = createSessionClientHost(oxyServices);
|
|
18
|
-
const transport = createTokenTransport(oxyServices);
|
|
19
|
-
const client = new SessionClient(host, { transport });
|
|
39
|
+
const transport = createTokenTransport(oxyServices, store);
|
|
40
|
+
const client = new SessionClient(host, { transport, socketFactory: io, onUnauthenticated });
|
|
20
41
|
return { client, host };
|
|
21
42
|
}
|
package/src/ui/session/index.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session-sync integration layer
|
|
2
|
+
* Session-sync integration layer — intra-package use only.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* `@oxyhq/core` owns the platform-agnostic `SessionClient`, its host adapter,
|
|
5
|
+
* and the pure `DeviceSessionState → services` projection helpers; those are
|
|
6
|
+
* re-exported straight from core here so `OxyContext` has one import site.
|
|
7
|
+
* `@oxyhq/services` supplies only the two platform pieces: the thin
|
|
8
|
+
* `createSessionClient` factory (injects socket.io-client `io` + the
|
|
9
|
+
* device-first token transport) and the `AuthStateStore` factory.
|
|
10
10
|
*/
|
|
11
11
|
export { createSessionClient } from './createSessionClient';
|
|
12
|
-
export { createSessionClientHost } from './sessionClientHost';
|
|
13
12
|
export { createTokenTransport } from './tokenTransport';
|
|
13
|
+
export { createPlatformAuthStateStore } from './authStore';
|
|
14
14
|
export {
|
|
15
|
+
createSessionClientHost,
|
|
15
16
|
accountIdsOf,
|
|
16
17
|
activeSessionIdOf,
|
|
17
18
|
activeUserOf,
|
|
18
19
|
deviceStateToClientSessions,
|
|
19
|
-
} from '
|
|
20
|
+
} from '@oxyhq/core';
|
|
@@ -1,42 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
refreshPersistedSession,
|
|
3
|
+
logger,
|
|
4
|
+
type AuthStateStore,
|
|
5
|
+
type OxyServices,
|
|
6
|
+
type TokenTransport,
|
|
7
|
+
} from '@oxyhq/core';
|
|
1
8
|
import type { DeviceSessionState } from '@oxyhq/contracts';
|
|
2
|
-
import type { OxyServices, TokenTransport } from '@oxyhq/core';
|
|
3
|
-
import { logger } from '@oxyhq/core';
|
|
4
|
-
import { isWebBrowser } from '../hooks/useWebSSO';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
|
-
* Platform `TokenTransport` for `SessionClient
|
|
8
|
-
* none is currently held, reusing the SAME primitives `OxyContext`'s cold
|
|
9
|
-
* boot already relies on (never re-implemented here):
|
|
11
|
+
* Platform `TokenTransport` for `SessionClient` (device-first model).
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* `ensureActiveToken` is the fallback the client uses when a `session_state`
|
|
14
|
+
* push arrived WITHOUT an embedded `activeToken` and the app currently holds no
|
|
15
|
+
* bearer. It mints one through the ONE unified refresh path
|
|
16
|
+
* (`refreshPersistedSession`): rotate the persisted refresh-token family
|
|
17
|
+
* (`POST /auth/refresh-token`) and, on native, fall back to the shared-keychain
|
|
18
|
+
* re-mint. There is no FedCM/silent-iframe arm anymore — the per-origin
|
|
19
|
+
* persisted refresh token is the durable web credential.
|
|
13
20
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `ensureActiveToken` treats a PRESENT token as sufficient in this phase; it
|
|
18
|
-
* does not decode/match the token's subject against `state.activeAccountId`
|
|
19
|
-
* (that refinement is Fase 3-B). Account switching itself is server-driven
|
|
20
|
-
* via the `activeToken` carried in the sync envelope — this transport is
|
|
21
|
-
* only the fallback used when the envelope carried no token.
|
|
22
|
-
*
|
|
23
|
-
* A failed mint is logged and swallowed: it must never throw out of
|
|
24
|
-
* `ensureActiveToken`, since that would crash the caller (the SessionClient
|
|
25
|
-
* socket handler in Fase 3-B).
|
|
21
|
+
* Concurrent pushes coalesce onto one in-flight mint. A failure is logged and
|
|
22
|
+
* swallowed: this method must never throw out (it runs inside the socket state
|
|
23
|
+
* handler).
|
|
26
24
|
*/
|
|
27
|
-
export function createTokenTransport(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// shared-key challenge round-trip.
|
|
25
|
+
export function createTokenTransport(
|
|
26
|
+
oxyServices: OxyServices,
|
|
27
|
+
store: AuthStateStore,
|
|
28
|
+
): TokenTransport {
|
|
32
29
|
let inFlightMint: Promise<void> | null = null;
|
|
33
30
|
|
|
34
31
|
return {
|
|
35
32
|
async ensureActiveToken(state: DeviceSessionState): Promise<void> {
|
|
36
|
-
// Read the current token defensively: it is an in-memory getter that
|
|
37
|
-
// should never throw, but the documented contract is that this method
|
|
38
|
-
// never throws out — so a surprising throw is logged and treated as "no
|
|
39
|
-
// token" (fall through to mint) rather than rejecting the promise.
|
|
40
33
|
try {
|
|
41
34
|
if (oxyServices.getAccessToken()) {
|
|
42
35
|
return;
|
|
@@ -51,18 +44,15 @@ export function createTokenTransport(oxyServices: OxyServices): TokenTransport {
|
|
|
51
44
|
|
|
52
45
|
inFlightMint = (async () => {
|
|
53
46
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
:
|
|
57
|
-
|
|
58
|
-
if (!session) {
|
|
59
|
-
logger.debug('ensureActiveToken: platform mint returned no session', {
|
|
47
|
+
const token = await refreshPersistedSession({ oxy: oxyServices, store });
|
|
48
|
+
if (!token) {
|
|
49
|
+
logger.debug('ensureActiveToken: refresh produced no session', {
|
|
60
50
|
component: 'TokenTransport',
|
|
61
51
|
deviceId: state.deviceId,
|
|
62
52
|
});
|
|
63
53
|
}
|
|
64
54
|
} catch (error) {
|
|
65
|
-
logger.warn('ensureActiveToken:
|
|
55
|
+
logger.warn('ensureActiveToken: refresh failed', { component: 'TokenTransport' }, error);
|
|
66
56
|
}
|
|
67
57
|
})();
|
|
68
58
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-browser detection for `@oxyhq/services`.
|
|
3
|
+
*
|
|
4
|
+
* The predicate now lives ONCE in `@oxyhq/core` (`isWebBrowser`) so services and
|
|
5
|
+
* auth-sdk share the exact same DOM probe. This module re-exposes it under the
|
|
6
|
+
* existing internal import path so consumers stay unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export { isWebBrowser } from '@oxyhq/core';
|