@oxy.so/services 11.0.0 → 11.2.0-oxy-one.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.
Files changed (145) hide show
  1. package/lib/commonjs/index.js +13 -0
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/boot/runProviderColdBoot.js +14 -1
  4. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxySignInButton.js +25 -7
  6. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  7. package/lib/commonjs/ui/context/OxyContext.js +198 -41
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/context/commitSessionFlow.js +5 -0
  10. package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
  11. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +56 -9
  12. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  13. package/lib/commonjs/ui/context/isolatedOAuthSession.js +52 -0
  14. package/lib/commonjs/ui/context/isolatedOAuthSession.js.map +1 -0
  15. package/lib/commonjs/ui/context/useOxyAccountGraph.js +7 -5
  16. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/queries/usePersonalPlans.js +43 -0
  18. package/lib/commonjs/ui/hooks/queries/usePersonalPlans.js.map +1 -0
  19. package/lib/commonjs/ui/hooks/useAuth.js +7 -3
  20. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  21. package/lib/commonjs/ui/oauth/applicationSessionLane.js +32 -0
  22. package/lib/commonjs/ui/oauth/applicationSessionLane.js.map +1 -0
  23. package/lib/commonjs/ui/oauth/completeOAuthCode.js +4 -2
  24. package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -1
  25. package/lib/commonjs/ui/oauth/nativeAuthTransport.js +119 -0
  26. package/lib/commonjs/ui/oauth/nativeAuthTransport.js.map +1 -0
  27. package/lib/commonjs/ui/session/createSessionClient.js +14 -2
  28. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  29. package/lib/commonjs/ui/session/deviceCredentialTracker.js +8 -0
  30. package/lib/commonjs/ui/session/deviceCredentialTracker.js.map +1 -1
  31. package/lib/module/index.js +1 -0
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/ui/boot/runProviderColdBoot.js +14 -1
  34. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  35. package/lib/module/ui/components/OxySignInButton.js +25 -7
  36. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  37. package/lib/module/ui/context/OxyContext.js +200 -43
  38. package/lib/module/ui/context/OxyContext.js.map +1 -1
  39. package/lib/module/ui/context/commitSessionFlow.js +5 -0
  40. package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
  41. package/lib/module/ui/context/hooks/useAuthOperations.js +56 -9
  42. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  43. package/lib/module/ui/context/isolatedOAuthSession.js +44 -0
  44. package/lib/module/ui/context/isolatedOAuthSession.js.map +1 -0
  45. package/lib/module/ui/context/useOxyAccountGraph.js +7 -5
  46. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  47. package/lib/module/ui/hooks/queries/usePersonalPlans.js +39 -0
  48. package/lib/module/ui/hooks/queries/usePersonalPlans.js.map +1 -0
  49. package/lib/module/ui/hooks/useAuth.js +7 -3
  50. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  51. package/lib/module/ui/oauth/applicationSessionLane.js +26 -0
  52. package/lib/module/ui/oauth/applicationSessionLane.js.map +1 -0
  53. package/lib/module/ui/oauth/completeOAuthCode.js +4 -2
  54. package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -1
  55. package/lib/module/ui/oauth/nativeAuthTransport.js +115 -0
  56. package/lib/module/ui/oauth/nativeAuthTransport.js.map +1 -0
  57. package/lib/module/ui/session/createSessionClient.js +14 -2
  58. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  59. package/lib/module/ui/session/deviceCredentialTracker.js +8 -0
  60. package/lib/module/ui/session/deviceCredentialTracker.js.map +1 -1
  61. package/lib/typescript/commonjs/index.d.ts +2 -0
  62. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +7 -0
  64. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +2 -0
  66. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -1
  68. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +2 -0
  70. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +3 -2
  72. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/ui/context/isolatedOAuthSession.d.ts +19 -0
  74. package/lib/typescript/commonjs/ui/context/isolatedOAuthSession.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +3 -0
  76. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +2 -1
  78. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +6 -0
  80. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts +64 -0
  82. package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -0
  83. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/ui/oauth/applicationSessionLane.d.ts +6 -0
  85. package/lib/typescript/commonjs/ui/oauth/applicationSessionLane.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts +2 -2
  87. package/lib/typescript/commonjs/ui/oauth/nativeAuthTransport.d.ts +28 -0
  88. package/lib/typescript/commonjs/ui/oauth/nativeAuthTransport.d.ts.map +1 -0
  89. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +1 -1
  90. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/ui/session/deviceCredentialTracker.d.ts.map +1 -1
  92. package/lib/typescript/module/index.d.ts +2 -0
  93. package/lib/typescript/module/index.d.ts.map +1 -1
  94. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +7 -0
  95. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  96. package/lib/typescript/module/ui/components/OxySignInButton.d.ts +2 -0
  97. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  98. package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -1
  99. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  100. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +2 -0
  101. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
  102. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +3 -2
  103. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  104. package/lib/typescript/module/ui/context/isolatedOAuthSession.d.ts +19 -0
  105. package/lib/typescript/module/ui/context/isolatedOAuthSession.d.ts.map +1 -0
  106. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +3 -0
  107. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  108. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +2 -1
  109. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  110. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +6 -0
  111. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  112. package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts +64 -0
  113. package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -0
  114. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  115. package/lib/typescript/module/ui/oauth/applicationSessionLane.d.ts +6 -0
  116. package/lib/typescript/module/ui/oauth/applicationSessionLane.d.ts.map +1 -0
  117. package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts +2 -2
  118. package/lib/typescript/module/ui/oauth/nativeAuthTransport.d.ts +28 -0
  119. package/lib/typescript/module/ui/oauth/nativeAuthTransport.d.ts.map +1 -0
  120. package/lib/typescript/module/ui/session/createSessionClient.d.ts +1 -1
  121. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  122. package/lib/typescript/module/ui/session/deviceCredentialTracker.d.ts.map +1 -1
  123. package/package.json +30 -30
  124. package/src/index.ts +4 -0
  125. package/src/ui/boot/__tests__/coldBootNoIdpRedirect.test.ts +56 -2
  126. package/src/ui/boot/runProviderColdBoot.ts +22 -2
  127. package/src/ui/components/OxySignInButton.tsx +20 -4
  128. package/src/ui/context/OxyContext.tsx +171 -40
  129. package/src/ui/context/__tests__/commitSessionFlow.test.ts +13 -0
  130. package/src/ui/context/__tests__/isolatedOAuthSession.test.ts +35 -0
  131. package/src/ui/context/commitSessionFlow.ts +8 -0
  132. package/src/ui/context/hooks/useAuthOperations.ts +55 -9
  133. package/src/ui/context/isolatedOAuthSession.ts +48 -0
  134. package/src/ui/context/oxyContextTypes.ts +4 -0
  135. package/src/ui/context/useOxyAccountGraph.ts +8 -5
  136. package/src/ui/hooks/queries/usePersonalPlans.ts +23 -0
  137. package/src/ui/hooks/useAuth.ts +6 -2
  138. package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +1 -1
  139. package/src/ui/oauth/__tests__/nativeAuthTransport.test.ts +57 -0
  140. package/src/ui/oauth/__tests__/oauthBearerCommit.http.test.ts +131 -0
  141. package/src/ui/oauth/applicationSessionLane.ts +26 -0
  142. package/src/ui/oauth/completeOAuthCode.ts +3 -3
  143. package/src/ui/oauth/nativeAuthTransport.ts +67 -0
  144. package/src/ui/session/createSessionClient.ts +13 -1
  145. package/src/ui/session/deviceCredentialTracker.ts +7 -1
@@ -0,0 +1,67 @@
1
+ import type { OxyServices } from '@oxy.so/core';
2
+ import { openAuthorizeUrlNative } from '../components/oauthNavigation';
3
+ import { resolveApplicationSessionLane } from './applicationSessionLane';
4
+ import { completeOAuthCode } from './completeOAuthCode';
5
+ import { prepareAuthorizeRequest } from './oauthHandshake';
6
+ import type { OAuthSessionCommitInput } from './types';
7
+
8
+ export interface StartNativeOAuthSignInOptions {
9
+ /** Exact registered URI, including any fixed query. Never inferred. */
10
+ redirectUri: string;
11
+ }
12
+ export type NativeOAuthSignInResult =
13
+ | { status: 'signed-in' }
14
+ | { status: 'cancelled' }
15
+ | { status: 'unsupported'; reason: 'unsupported-platform' | 'missing-client-id' | 'identity-bound' | 'not-third-party' }
16
+ | { status: 'failed'; reason: 'invalid-redirect-uri' | 'client-resolution-failed' | 'native-callback-invalid' | 'state-mismatch' | 'exchange-failed' | 'already-in-progress' };
17
+ export interface NativeOAuthSignInContext {
18
+ platform: 'native' | 'unsupported';
19
+ oxyServices: OxyServices;
20
+ clientId: string | null;
21
+ authorizeBaseUrl?: string;
22
+ identityBound: boolean;
23
+ commitSession: (input: OAuthSessionCommitInput) => Promise<void>;
24
+ }
25
+ const attempts = new WeakSet<OxyServices>();
26
+
27
+ function isExactRedirectUri(value: string): boolean {
28
+ if (!value || value !== value.trim() || /\s/.test(value)) return false;
29
+ try {
30
+ const uri = new URL(value);
31
+ return !uri.username && !uri.password && !uri.hash &&
32
+ !['code', 'state', 'error', 'error_description'].some((key) => uri.searchParams.has(key));
33
+ } catch { return false; }
34
+ }
35
+
36
+ /** SDK owns the in-memory handshake, observed native callback and session commit. */
37
+ export async function startNativeOAuthSignIn(
38
+ context: NativeOAuthSignInContext,
39
+ options: StartNativeOAuthSignInOptions,
40
+ ): Promise<NativeOAuthSignInResult> {
41
+ if (context.platform !== 'native') return { status: 'unsupported', reason: 'unsupported-platform' };
42
+ if (!context.clientId) return { status: 'unsupported', reason: 'missing-client-id' };
43
+ if (context.identityBound) return { status: 'unsupported', reason: 'identity-bound' };
44
+ if (!isExactRedirectUri(options.redirectUri)) return { status: 'failed', reason: 'invalid-redirect-uri' };
45
+ if (attempts.has(context.oxyServices)) return { status: 'failed', reason: 'already-in-progress' };
46
+ attempts.add(context.oxyServices);
47
+ try {
48
+ let lane: 'device' | 'oauth';
49
+ try { lane = await resolveApplicationSessionLane(context.oxyServices, context.clientId); }
50
+ catch { return { status: 'failed', reason: 'client-resolution-failed' }; }
51
+ if (lane !== 'oauth') return { status: 'unsupported', reason: 'not-third-party' };
52
+ const prepared = await prepareAuthorizeRequest({ clientId: context.clientId, redirectUri: options.redirectUri, authorizeBaseUrl: context.authorizeBaseUrl });
53
+ const { redirectUrl } = await openAuthorizeUrlNative(prepared.authorizeUrl, options.redirectUri, { allowExternalFallback: false });
54
+ if (!redirectUrl) return { status: 'cancelled' };
55
+ const prefix = `${options.redirectUri}${options.redirectUri.includes('?') ? '&' : '?'}`;
56
+ if (!redirectUrl.startsWith(prefix) || redirectUrl.includes('#')) return { status: 'failed', reason: 'native-callback-invalid' };
57
+ const parameters = new URLSearchParams(redirectUrl.slice(prefix.length));
58
+ if ([...new URL(options.redirectUri).searchParams.keys()].some((key) => parameters.has(key))) return { status: 'failed', reason: 'native-callback-invalid' };
59
+ if (['code', 'state', 'error', 'error_description'].some((key) => parameters.getAll(key).length > 1)) return { status: 'failed', reason: 'native-callback-invalid' };
60
+ if (parameters.get('state') !== prepared.handshake.state) return { status: 'failed', reason: 'state-mismatch' };
61
+ const code = parameters.get('code');
62
+ if (parameters.has('error') || !code) return { status: 'failed', reason: 'native-callback-invalid' };
63
+ const completion = await completeOAuthCode({ oxyServices: context.oxyServices, clientId: context.clientId, code, returnedState: parameters.get('state'), handshake: prepared.handshake, redirectUri: options.redirectUri, commitSession: context.commitSession });
64
+ return completion.ok ? { status: 'signed-in' } : { status: 'failed', reason: completion.reason };
65
+ } catch { return { status: 'failed', reason: 'exchange-failed' }; }
66
+ finally { attempts.delete(context.oxyServices); }
67
+ }
@@ -40,15 +40,27 @@ export function createSessionClient(
40
40
  oxyServices: OxyServices,
41
41
  onUnauthenticated?: (origin: SessionStateOrigin) => void,
42
42
  getPinnedAccountId?: () => string | null,
43
+ isDeviceSessionAllowed: () => boolean = () => true,
44
+ onFullExplicitSignOut?: () => Promise<void>,
43
45
  ): {
44
46
  client: SessionClient;
45
47
  host: ReturnType<typeof createSessionClientHost>;
46
48
  } {
47
- const host = createSessionClientHost(oxyServices);
49
+ const baseHost = createSessionClientHost(oxyServices);
50
+ const host = {
51
+ ...baseHost,
52
+ getAccessToken: () => isDeviceSessionAllowed() ? baseHost.getAccessToken() : null,
53
+ setTokens: (token: string) => { if (isDeviceSessionAllowed()) baseHost.setTokens(token); },
54
+ makeRequest: async <T>(method: 'GET' | 'POST', url: string, data?: unknown, requestOptions?: { cache?: boolean }): Promise<T> => {
55
+ if (!isDeviceSessionAllowed()) throw new Error('An isolated OAuth session cannot access the shared device');
56
+ return baseHost.makeRequest<T>(method, url, data, requestOptions);
57
+ },
58
+ };
48
59
  const transport = createTokenTransport(oxyServices, getPinnedAccountId);
49
60
  const client = new SessionClient(host, {
50
61
  transport,
51
62
  onUnauthenticated,
63
+ onFullExplicitSignOut,
52
64
  getPinnedAccountId,
53
65
  });
54
66
  return { client, host };
@@ -7,7 +7,7 @@
7
7
  * every load, save and clear passes through, so the press can read it at once.
8
8
  * The cold boot loads the store on mount, long before anyone presses sign-in.
9
9
  */
10
- import type { AuthStateStore } from '@oxy.so/core/session';
10
+ import type { AuthStateStore, AuthStateWriteGuard, PersistedAuthState } from '@oxy.so/core/session';
11
11
 
12
12
  export interface HeldDeviceCredential {
13
13
  deviceId: string;
@@ -31,6 +31,7 @@ function credentialOf(state: { deviceId?: string; deviceSecret?: string } | null
31
31
  export function trackDeviceCredential(store: AuthStateStore): CredentialTrackingAuthStateStore {
32
32
  let held: HeldDeviceCredential | null | undefined;
33
33
  return {
34
+ ...store,
34
35
  load: async () => {
35
36
  const state = await store.load();
36
37
  held = credentialOf(state);
@@ -40,6 +41,11 @@ export function trackDeviceCredential(store: AuthStateStore): CredentialTracking
40
41
  held = credentialOf(state);
41
42
  return store.save(state);
42
43
  },
44
+ ...(store.saveIfCurrent ? {saveIfCurrent: async (state: PersistedAuthState, guard: AuthStateWriteGuard) => {
45
+ const committed = await store.saveIfCurrent?.(state, guard);
46
+ if (committed) held = credentialOf(state);
47
+ return committed === true;
48
+ }} : {}),
43
49
  clear: async () => {
44
50
  held = null;
45
51
  await store.clear();