@oxyhq/core 7.1.1 → 8.1.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 (93) hide show
  1. package/README.md +48 -24
  2. package/dist/cjs/.tsbuildinfo +1 -1
  3. package/dist/cjs/HttpService.js +6 -6
  4. package/dist/cjs/boot/coldBootV2.js +97 -2
  5. package/dist/cjs/boot/deviceBootReturn.js +15 -0
  6. package/dist/cjs/i18n/locales/en-US.json +44 -1
  7. package/dist/cjs/i18n/locales/es-ES.json +44 -1
  8. package/dist/cjs/i18n/locales/locales/en-US.json +45 -2
  9. package/dist/cjs/i18n/locales/locales/es-ES.json +45 -2
  10. package/dist/cjs/index.js +19 -16
  11. package/dist/cjs/mixins/OxyServices.deviceBoot.js +28 -0
  12. package/dist/cjs/server/index.js +1 -7
  13. package/dist/cjs/session/accountDialogController.js +1 -1
  14. package/dist/cjs/session/accountProjection.js +1 -1
  15. package/dist/cjs/session/authStateStore.js +6 -0
  16. package/dist/cjs/session/projectSessionState.js +1 -1
  17. package/dist/cjs/session/refresh.js +9 -0
  18. package/dist/cjs/session/sessionClientHost.js +1 -2
  19. package/dist/cjs/utils/accountUtils.js +1 -1
  20. package/dist/cjs/utils/oauthPkce.js +142 -0
  21. package/dist/cjs/utils/platform.js +1 -1
  22. package/dist/esm/.tsbuildinfo +1 -1
  23. package/dist/esm/HttpService.js +6 -6
  24. package/dist/esm/boot/coldBootV2.js +97 -2
  25. package/dist/esm/boot/deviceBootReturn.js +15 -0
  26. package/dist/esm/i18n/locales/en-US.json +44 -1
  27. package/dist/esm/i18n/locales/es-ES.json +44 -1
  28. package/dist/esm/i18n/locales/locales/en-US.json +45 -2
  29. package/dist/esm/i18n/locales/locales/es-ES.json +45 -2
  30. package/dist/esm/index.js +11 -13
  31. package/dist/esm/mixins/OxyServices.deviceBoot.js +29 -1
  32. package/dist/esm/server/index.js +0 -5
  33. package/dist/esm/session/accountDialogController.js +1 -1
  34. package/dist/esm/session/accountProjection.js +1 -1
  35. package/dist/esm/session/authStateStore.js +6 -0
  36. package/dist/esm/session/projectSessionState.js +1 -1
  37. package/dist/esm/session/refresh.js +9 -0
  38. package/dist/esm/session/sessionClientHost.js +1 -2
  39. package/dist/esm/utils/accountUtils.js +1 -1
  40. package/dist/esm/utils/oauthPkce.js +135 -0
  41. package/dist/esm/utils/platform.js +1 -1
  42. package/dist/types/.tsbuildinfo +1 -1
  43. package/dist/types/HttpService.d.ts +1 -1
  44. package/dist/types/index.d.ts +3 -2
  45. package/dist/types/mixins/OxyServices.accounts.d.ts +13 -3
  46. package/dist/types/mixins/OxyServices.connectedApps.d.ts +4 -0
  47. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +17 -1
  48. package/dist/types/mixins/OxyServices.devices.d.ts +3 -2
  49. package/dist/types/models/interfaces.d.ts +4 -4
  50. package/dist/types/server/index.d.ts +0 -1
  51. package/dist/types/session/accountDialogController.d.ts +1 -1
  52. package/dist/types/session/accountProjection.d.ts +1 -1
  53. package/dist/types/session/authStateStore.d.ts +20 -0
  54. package/dist/types/session/projectSessionState.d.ts +1 -1
  55. package/dist/types/session/refresh.d.ts +4 -8
  56. package/dist/types/session/sessionClientHost.d.ts +1 -2
  57. package/dist/types/utils/accountUtils.d.ts +1 -1
  58. package/dist/types/utils/oauthPkce.d.ts +74 -0
  59. package/dist/types/utils/platform.d.ts +1 -1
  60. package/package.json +3 -3
  61. package/src/HttpService.ts +6 -6
  62. package/src/boot/__tests__/coldBootV2.test.ts +215 -1
  63. package/src/boot/__tests__/deviceBootReturn.test.ts +32 -0
  64. package/src/boot/coldBootV2.ts +117 -2
  65. package/src/boot/deviceBootReturn.ts +15 -0
  66. package/src/i18n/locales/en-US.json +45 -2
  67. package/src/i18n/locales/es-ES.json +45 -2
  68. package/src/index.ts +23 -16
  69. package/src/mixins/OxyServices.accounts.ts +12 -0
  70. package/src/mixins/OxyServices.connectedApps.ts +4 -0
  71. package/src/mixins/OxyServices.deviceBoot.ts +38 -0
  72. package/src/mixins/OxyServices.devices.ts +6 -5
  73. package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +44 -1
  74. package/src/mixins/__tests__/accounts.test.ts +1 -1
  75. package/src/models/interfaces.ts +7 -5
  76. package/src/server/index.ts +0 -6
  77. package/src/session/__tests__/authStateStore.test.ts +27 -0
  78. package/src/session/__tests__/refresh.test.ts +14 -0
  79. package/src/session/accountDialogController.ts +1 -1
  80. package/src/session/accountProjection.ts +1 -1
  81. package/src/session/authStateStore.ts +26 -0
  82. package/src/session/projectSessionState.ts +1 -1
  83. package/src/session/refresh.ts +13 -8
  84. package/src/session/sessionClientHost.ts +1 -2
  85. package/src/utils/__tests__/coldBoot.test.ts +55 -65
  86. package/src/utils/__tests__/oauthPkce.test.ts +154 -0
  87. package/src/utils/accountUtils.ts +1 -1
  88. package/src/utils/oauthPkce.ts +189 -0
  89. package/src/utils/platform.ts +1 -1
  90. package/dist/cjs/utils/ssoBounce.js +0 -24
  91. package/dist/esm/utils/ssoBounce.js +0 -21
  92. package/dist/types/utils/ssoBounce.d.ts +0 -21
  93. package/src/utils/ssoBounce.ts +0 -22
@@ -85,7 +85,7 @@ export declare class HttpService {
85
85
  private cacheSizeWarningSilentUntil;
86
86
  /**
87
87
  * Fan-out listeners notified on EVERY access-token change on this instance:
88
- * explicit `setTokens`, `clearTokens`, an AuthManager-owned refresh, and the
88
+ * explicit `setTokens`, `clearTokens`, a refresh-handler rotation, and the
89
89
  * internal 401-driven clear. This is a Set so multiple independent observers
90
90
  * can mirror token state without clobbering each other.
91
91
  *
@@ -53,7 +53,7 @@ export { RecoveryPhraseService } from './crypto/recoveryPhrase';
53
53
  export type { RecoveryPhraseResult } from './crypto/recoveryPhrase';
54
54
  export { DeviceManager } from './utils/deviceManager';
55
55
  export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
56
- export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceSession, DeviceSessionsResponse, DeviceSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
56
+ export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferences, User, LoginResponse, Notification, Wallet, Transaction, BlockedUser, RestrictedUser, TransferFundsRequest, PurchaseRequest, WithdrawalRequest, TransactionResponse, PaginationInfo, SearchProfilesResponse, ApiError, PaymentMethod, PaymentRequest, PaymentResponse, AnalyticsData, FollowerDetails, ContentViewer, FileMetadata, FileUploadResponse, FileListResponse, FileUpdateRequest, FileDeleteResponse, RNFileDescriptor, AssetUploadInput, FileVisibility, AssetLink, AssetMetadata, AssetVariant, Asset, AssetInitRequest, AssetInitResponse, AssetCompleteRequest, AssetLinkRequest, AssetUnlinkRequest, AssetUrlResponse, AssetDeleteSummary, AssetUpdateVisibilityRequest, AssetUpdateVisibilityResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, AccountStorageCategoryUsage, AccountStorageUsageResponse, SecurityEventType, SecurityEventSeverity, SecurityActivity, SecurityActivityResponse, AssetUploadProgress, DeviceLinkedSession, DeviceLinkedSessionsResponse, DeviceLinkedSessionLogoutResponse, UpdateDeviceNameResponse, } from './models/interfaces';
57
57
  export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
58
58
  export { TopicType, TopicSource } from './models/Topic';
59
59
  export type { TopicData, TopicTranslation } from './models/Topic';
@@ -81,9 +81,10 @@ export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccou
81
81
  export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
82
82
  export { registrableApex } from './utils/registrableApex';
83
83
  export { CENTRAL_IDP_APEX } from './utils/authWebUrl';
84
- export { SSO_CALLBACK_PATH } from './utils/ssoBounce';
85
84
  export { runColdBoot } from './utils/coldBoot';
86
85
  export type { ColdBootStep, ColdBootStepResult, ColdBootSession, ColdBootSkip, ColdBootOutcome, RunColdBootOptions, } from './utils/coldBoot';
86
+ export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, } from './utils/oauthPkce';
87
+ export type { PkcePair, BuildOAuthAuthorizeUrlParams } from './utils/oauthPkce';
87
88
  export { SessionClient } from './session/SessionClient';
88
89
  export type { TokenTransport, SessionClientHost, SessionClientOptions } from './session/SessionClient';
89
90
  export type { SocketIOFactory, MinimalSocket } from './session/socketLoader';
@@ -250,6 +250,10 @@ export interface Application {
250
250
  name: string;
251
251
  description?: string;
252
252
  websiteUrl?: string;
253
+ /** Public privacy-policy URL, rendered as a legal link on the OAuth consent screen. */
254
+ privacyPolicyUrl?: string;
255
+ /** Public terms-of-service URL, rendered as a legal link on the OAuth consent screen. */
256
+ termsUrl?: string;
253
257
  icon?: string;
254
258
  type: ApplicationType;
255
259
  status: ApplicationStatus;
@@ -306,6 +310,10 @@ export interface CreateApplicationInput {
306
310
  name: string;
307
311
  description?: string;
308
312
  websiteUrl?: string;
313
+ /** Public privacy-policy URL (absolute `https://`). Shown on the OAuth consent screen. */
314
+ privacyPolicyUrl?: string;
315
+ /** Public terms-of-service URL (absolute `https://`). Shown on the OAuth consent screen. */
316
+ termsUrl?: string;
309
317
  icon?: string;
310
318
  redirectUris?: string[];
311
319
  scopes?: string[];
@@ -320,6 +328,10 @@ export interface UpdateApplicationInput {
320
328
  name?: string;
321
329
  description?: string;
322
330
  websiteUrl?: string;
331
+ /** Public privacy-policy URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
332
+ privacyPolicyUrl?: string;
333
+ /** Public terms-of-service URL (absolute `https://`, or `''` to clear). Shown on the OAuth consent screen. */
334
+ termsUrl?: string;
323
335
  icon?: string;
324
336
  redirectUris?: string[];
325
337
  scopes?: string[];
@@ -689,9 +701,7 @@ export declare function OxyServicesAccountsMixin<T extends typeof OxyServicesBas
689
701
  handleError(error: unknown): Error;
690
702
  healthCheck(): Promise<{
691
703
  status: string;
692
- users
693
- /** Aggregate totals for an application over the requested period. */
694
- ? /** Aggregate totals for an application over the requested period. */: number;
704
+ users?: number;
695
705
  timestamp?: string;
696
706
  [key: string]: any;
697
707
  }>;
@@ -37,6 +37,10 @@ export interface PublicApplication {
37
37
  icon?: string;
38
38
  /** Optional public website/homepage URL for the application. */
39
39
  websiteUrl?: string;
40
+ /** Optional public privacy-policy URL, rendered as a legal link on the consent screen. */
41
+ privacyPolicyUrl?: string;
42
+ /** Optional public terms-of-service URL, rendered as a legal link on the consent screen. */
43
+ termsUrl?: string;
40
44
  /** Application classification (set by Oxy platform staff). */
41
45
  type: ApplicationType;
42
46
  /** Whether the application is an officially endorsed Oxy application. */
@@ -15,7 +15,7 @@
15
15
  * and `setTokens`, so the same network primitive can be reused from either
16
16
  * without double-planting.
17
17
  */
18
- import { type AuthTokenBundle, type TokenRefreshResponse, type WebSessionResult } from '@oxyhq/contracts';
18
+ import { type AuthTokenBundle, type DeviceTokenMintResponse, type TokenRefreshResponse, type WebSessionResult } from '@oxyhq/contracts';
19
19
  import type { OxyServicesBase } from '../OxyServices.base';
20
20
  export declare function OxyServicesDeviceBootMixin<T extends typeof OxyServicesBase>(Base: T): {
21
21
  new (...args: any[]): {
@@ -51,6 +51,22 @@ export declare function OxyServicesDeviceBootMixin<T extends typeof OxyServicesB
51
51
  * @throws if the response does not match {@link deviceTokenIssueResponseSchema}.
52
52
  */
53
53
  issueNativeDeviceToken(): Promise<string>;
54
+ /**
55
+ * Zero-cookie mint (phase 2c). Present the first-party `deviceId` +
56
+ * `deviceSecret` to `POST /session/device/token` — NO bearer, NO cookies:
57
+ * possession of the secret IS the device-ownership proof. Returns a fresh
58
+ * short access token for the device's active account plus `nextDeviceSecret`
59
+ * (rotation-in-use) and the projected device-session `state`.
60
+ *
61
+ * `skipAuth` (like {@link refreshWithToken}): this call carries no bearer, so
62
+ * a 401 must surface DIRECTLY — never trigger `HttpService`'s 401→refresh→
63
+ * retry dance (which would pointlessly rotate the refresh family). The cold
64
+ * boot reads the 401 body (`invalid_device_secret` vs `no_active_session`) to
65
+ * decide whether to drop the secret and fall back or resolve signed-out.
66
+ *
67
+ * @throws if the response does not match {@link deviceTokenMintResponseSchema}.
68
+ */
69
+ mintFromDeviceSecret(deviceId: string, deviceSecret: string): Promise<DeviceTokenMintResponse>;
54
70
  /**
55
71
  * Build the top-level `GET /auth/device/bootstrap` URL for the cross-apex
56
72
  * hop. The server validates `return_to` against the trusted-origin lane and
@@ -2,6 +2,7 @@
2
2
  * Device Methods Mixin
3
3
  */
4
4
  import type { OxyServicesBase } from '../OxyServices.base';
5
+ import type { DeviceLinkedSession, DeviceLinkedSessionLogoutResponse } from '../models/interfaces';
5
6
  export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase>(Base: T): {
6
7
  new (...args: any[]): {
7
8
  /**
@@ -26,7 +27,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
26
27
  * @param sessionId - The session ID
27
28
  * @returns Array of device sessions
28
29
  */
29
- getDeviceSessions(sessionId: string): Promise<any[]>;
30
+ getDeviceSessions(sessionId: string): Promise<DeviceLinkedSession[]>;
30
31
  /**
31
32
  * Logout all device sessions
32
33
  * @param sessionId - The session ID
@@ -34,7 +35,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
34
35
  * @param excludeCurrent - Whether to exclude the current session
35
36
  * @returns Logout result
36
37
  */
37
- logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<any>;
38
+ logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<DeviceLinkedSessionLogoutResponse>;
38
39
  /**
39
40
  * Update device name
40
41
  * @param sessionId - The session ID
@@ -545,7 +545,7 @@ export interface AssetUploadProgress {
545
545
  status: 'uploading' | 'processing' | 'complete' | 'error';
546
546
  error?: string;
547
547
  }
548
- export interface DeviceSession {
548
+ export interface DeviceLinkedSession {
549
549
  sessionId: string;
550
550
  deviceId: string;
551
551
  deviceName: string;
@@ -556,11 +556,11 @@ export interface DeviceSession {
556
556
  user?: User;
557
557
  createdAt?: string;
558
558
  }
559
- export interface DeviceSessionsResponse {
559
+ export interface DeviceLinkedSessionsResponse {
560
560
  deviceId: string;
561
- sessions: DeviceSession[];
561
+ sessions: DeviceLinkedSession[];
562
562
  }
563
- export interface DeviceSessionLogoutResponse {
563
+ export interface DeviceLinkedSessionLogoutResponse {
564
564
  message: string;
565
565
  deviceId: string;
566
566
  sessionsTerminated: number;
@@ -24,4 +24,3 @@ export { createOxyCors } from './cors';
24
24
  export type { OxyCorsOptions } from './cors';
25
25
  export { verifySecret } from './verifySecret';
26
26
  export { registrableApex } from '../utils/registrableApex';
27
- export { SSO_CALLBACK_PATH } from '../utils/ssoBounce';
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A framework-agnostic state machine + subscribe/getSnapshot store (the same
5
5
  * pattern {@link SessionClient} uses — no React, no RN) that both
6
- * `@oxyhq/services` (RN `OxyProvider`) and `@oxyhq/auth` (web `WebOxyProvider`)
6
+ * every `OxyProvider` platform variant (Expo/RN and RN-Web)
7
7
  * bind to via `useSyncExternalStore`, so the account chooser is ONE
8
8
  * implementation across the ecosystem instead of the five drifting copies it
9
9
  * replaces.
@@ -5,7 +5,7 @@
5
5
  * merging the device's server-authoritative session set (`DeviceSessionState`
6
6
  * from {@link SessionClient}) with the caller's account graph (`AccountNode[]`
7
7
  * from `oxyServices.listAccounts()`), deduped by `accountId`. This lives in
8
- * `@oxyhq/core` so `@oxyhq/services` (RN) and `@oxyhq/auth` (web) — and
8
+ * `@oxyhq/core` so every `@oxyhq/services` platform variant — and
9
9
  * `auth.oxy.so` — all render the SAME list from the SAME logic and cannot
10
10
  * diverge.
11
11
  *
@@ -38,6 +38,26 @@ export interface PersistedAuthState {
38
38
  refreshToken: string;
39
39
  userId: string;
40
40
  deviceToken?: string;
41
+ /**
42
+ * The stable device identifier this session is bound to (phase 2c —
43
+ * zero-cookie transport). Persisted alongside {@link deviceSecret} because the
44
+ * `POST /session/device/token` mint presents BOTH — the secret is the proof,
45
+ * the deviceId selects the device doc. Sourced from the lanes that carry it
46
+ * (password login / 2FA / QR claim / challenge verify); the cookie-bootstrap
47
+ * lanes (`AuthTokenBundle`) omit it and preserve any prior value. Additive: a
48
+ * blob without it simply never takes the mint lane and falls back to the
49
+ * refresh family.
50
+ */
51
+ deviceId?: string;
52
+ /**
53
+ * The rotating device secret (phase 2c — zero-cookie transport). Possession of
54
+ * it mints a short access token for the device's active account via
55
+ * `POST /session/device/token`, replacing the cookie lane. Rotated in-use: the
56
+ * mint returns `nextDeviceSecret`, which the cold boot persists BEFORE planting
57
+ * the minted access token (multi-tab anti-loss). Additive and optional — same
58
+ * XSS risk profile as the already-persisted `refreshToken`.
59
+ */
60
+ deviceSecret?: string;
41
61
  /** Optional warm-boot access token (short-lived; see interface docs). */
42
62
  accessToken?: string;
43
63
  /** Optional warm-boot access-token expiry, ISO-8601. */
@@ -4,7 +4,7 @@ import type { User } from '../models/interfaces';
4
4
  /**
5
5
  * Pure projection helpers: `DeviceSessionState` (the device-scoped
6
6
  * multi-account session-sync state produced by `SessionClient`) -> the
7
- * shapes consumers (`@oxyhq/services`, `@oxyhq/auth`) render today
7
+ * shapes `@oxyhq/services` consumers render today
8
8
  * (`ClientSession[]`, an active session id, an active `User`).
9
9
  *
10
10
  * No I/O. The caller fetches profiles via
@@ -1,11 +1,8 @@
1
1
  /**
2
2
  * Unified token refresh — THE single refresh implementation for web + native.
3
3
  *
4
- * Before device-first, refresh was duplicated: `@oxyhq/auth`'s
5
- * `session/tokenRefresh.ts` (per-apex `/auth/silent` iframe) and
6
- * `@oxyhq/services`'s `inSessionTokenRefresh.ts` (native shared-key). This
7
- * module replaces both with ONE persisted-refresh-token rotation shared by
8
- * every consumer:
4
+ * ONE persisted-refresh-token rotation shared by every consumer (it replaced
5
+ * the pre-device-first per-platform duplicates):
9
6
  *
10
7
  * - `refreshPersistedSession` — arm 1 rotates the stored refresh-token family
11
8
  * (`POST /auth/refresh-token`), planting + persisting the rotated pair; arm 2
@@ -16,9 +13,8 @@
16
13
  * - `createAuthRefreshHandler` / `installAuthRefreshHandler` wire arm 1+2 into
17
14
  * `HttpService.setAuthRefreshHandler`, keeping that layer's single-flight
18
15
  * dedup + cooldown (this module does NOT reimplement them).
19
- * - `startTokenRefreshScheduler` — a proactive scheduler (lifted from the
20
- * better of the two prior duplicates, `@oxyhq/auth`'s `tokenRefresh.ts`),
21
- * decoupled from any React / auth-sdk type: refreshes ~60s before `exp`,
16
+ * - `startTokenRefreshScheduler` — a proactive scheduler decoupled from any
17
+ * React type: refreshes ~60s before `exp`,
22
18
  * re-arms on token change + web tab-focus, `.unref?.()`s its timer in Node.
23
19
  *
24
20
  * Framework-free; no module-level mutable state.
@@ -6,8 +6,7 @@ import type { SessionClientHost } from './SessionClient';
6
6
  * `SessionClient` is host-agnostic: it only needs a REST + token surface.
7
7
  * `OxyServices` already exposes all of that except `getCurrentAccountId`,
8
8
  * which has no direct equivalent — the adapter holds a mutable ref set by
9
- * the caller (`OxyContext` in `@oxyhq/services`, `WebOxyProvider` in
10
- * `@oxyhq/auth`) via `setCurrentAccountId`.
9
+ * the caller (`OxyContext` in `@oxyhq/services`) via `setCurrentAccountId`.
11
10
  *
12
11
  * Shared here (rather than duplicated per consumer) because it is entirely
13
12
  * platform-agnostic: every method it calls exists identically on
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Shared account types and pure helper functions.
3
- * Used by both @oxyhq/services (React Native) and @oxyhq/auth (Web) account stores.
3
+ * Used by the @oxyhq/services account stores (Expo/RN and RN-Web).
4
4
  */
5
5
  export interface QuickAccount {
6
6
  sessionId: string;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * OAuth 2.0 Authorization Code + PKCE helpers for "Sign in with Oxy" third-party
3
+ * sign-in.
4
+ *
5
+ * Third-party Relying Parties (SPAs, static sites, and native apps that are NOT
6
+ * Oxy first-party) authenticate through the standard OAuth flow against
7
+ * `auth.oxy.so/authorize` — never FedCM, SSO bounces, or Oxy session cookies.
8
+ * Public clients (no secret) prove possession of the authorization code with
9
+ * PKCE (RFC 7636, S256): the RP generates a random `code_verifier`, sends its
10
+ * `code_challenge = BASE64URL(SHA-256(code_verifier))` on the authorize
11
+ * redirect, and later replays the raw verifier on the token exchange.
12
+ *
13
+ * All cross-platform crypto (random bytes, SHA-256) is delegated to the shared
14
+ * `@oxyhq/protocol` platform loaders — the exact primitives the rest of core's
15
+ * crypto already uses — so these helpers run identically on web, Node, and
16
+ * React Native. No `require()`, so the ESM build stays bundler-clean.
17
+ */
18
+ /** The central Oxy IdP authorization endpoint used by default. */
19
+ export declare const OXY_AUTHORIZE_URL = "https://auth.oxy.so/authorize";
20
+ /** Default OAuth scope requested for a "Sign in with Oxy" third-party flow. */
21
+ export declare const DEFAULT_OAUTH_SCOPE = "openid profile";
22
+ /** A generated PKCE verifier/challenge pair. */
23
+ export interface PkcePair {
24
+ /** The high-entropy secret replayed on the token exchange (kept client-side). */
25
+ codeVerifier: string;
26
+ /** `BASE64URL(SHA-256(codeVerifier))` — sent on the authorize redirect. */
27
+ codeChallenge: string;
28
+ /** The PKCE transformation method. Always `S256`. */
29
+ method: 'S256';
30
+ }
31
+ /** Parameters for {@link buildOAuthAuthorizeUrl}. */
32
+ export interface BuildOAuthAuthorizeUrlParams {
33
+ /** Authorize endpoint; defaults to {@link OXY_AUTHORIZE_URL}. */
34
+ authorizeBaseUrl?: string;
35
+ /** The registered `ApplicationCredential` public key (`oxy_dk_…`). */
36
+ clientId: string;
37
+ /** Exact registered redirect URI to return the authorization code to. */
38
+ redirectUri: string;
39
+ /** Requested scope; defaults to {@link DEFAULT_OAUTH_SCOPE}. */
40
+ scope?: string;
41
+ /** Opaque CSRF token from {@link generateOAuthState}. */
42
+ state: string;
43
+ /** The PKCE `codeChallenge` from {@link generatePkcePair}. */
44
+ codeChallenge: string;
45
+ }
46
+ /**
47
+ * Compute the PKCE S256 `code_challenge` for a given verifier:
48
+ * `BASE64URL(SHA-256(ASCII(codeVerifier)))` (RFC 7636 §4.2). The verifier is
49
+ * base64url (ASCII), so its UTF-8 and ASCII byte encodings are identical.
50
+ *
51
+ * Reuses `@oxyhq/protocol`'s cross-platform {@link sha256} (which returns
52
+ * lowercase hex); the digest bytes are recovered and re-encoded as base64url.
53
+ */
54
+ export declare function computeCodeChallenge(codeVerifier: string): Promise<string>;
55
+ /**
56
+ * Generate a fresh PKCE verifier/challenge pair for an OAuth authorization-code
57
+ * flow. The verifier is 64 random bytes as base64url (86 chars, within RFC 7636
58
+ * §4.1's 43–128 range and drawn only from the unreserved set); the challenge is
59
+ * its S256 transform.
60
+ */
61
+ export declare function generatePkcePair(): Promise<PkcePair>;
62
+ /**
63
+ * Generate an opaque, single-use OAuth `state` token (32 random bytes as
64
+ * base64url) for CSRF protection across the authorize redirect.
65
+ */
66
+ export declare function generateOAuthState(): Promise<string>;
67
+ /**
68
+ * Build the `auth.oxy.so/authorize` redirect URL for an OAuth authorization-code
69
+ * + PKCE (S256) flow. Built via the WHATWG `URL` API so a custom
70
+ * `authorizeBaseUrl` that already carries a query string keeps its existing
71
+ * params (the OAuth params are merged in, not clobbered by a naive `?` concat).
72
+ * All values are percent-encoded by `URL.searchParams`.
73
+ */
74
+ export declare function buildOAuthAuthorizeUrl(params: BuildOAuthAuthorizeUrlParams): string;
@@ -39,7 +39,7 @@ export declare function setPlatformOS(os: PlatformOS): void;
39
39
  *
40
40
  * Native defines a global `window` but no `document`, so the DOM probe — not a
41
41
  * bare `window` check — is the reliable discriminator. This is the single
42
- * source of truth consumed by `@oxyhq/services` and `@oxyhq/auth` (both dropped
42
+ * source of truth consumed by `@oxyhq/services` (which dropped
43
43
  * their local copies), so every consumer shares the exact same predicate.
44
44
  *
45
45
  * NOTE: this is a live runtime probe (not the cached `getPlatformOS()` verdict)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "7.1.1",
3
+ "version": "8.1.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -94,8 +94,8 @@
94
94
  }
95
95
  },
96
96
  "dependencies": {
97
- "@oxyhq/contracts": "^0.10.0",
98
- "@oxyhq/protocol": "^0.1.1",
97
+ "@oxyhq/contracts": "^0.12.0",
98
+ "@oxyhq/protocol": "^0.1.2",
99
99
  "bip39": "^3.1.0",
100
100
  "buffer": "^6.0.3",
101
101
  "elliptic": "^6.6.1",
@@ -128,7 +128,7 @@ const CSRF_FETCH_RETRY_DELAY_MS = 500;
128
128
  /**
129
129
  * Cooldown (ms) applied after a failed access-token refresh before another
130
130
  * refresh is attempted. Prevents a refresh storm (and server hammering) when
131
- * the AuthManager's refresh handler is failing — every in-flight request that
131
+ * the auth refresh handler is failing — every in-flight request that
132
132
  * hits a 401 would otherwise trigger its own refresh.
133
133
  */
134
134
  const TOKEN_REFRESH_COOLDOWN_MS = 15000;
@@ -244,7 +244,7 @@ export class HttpService {
244
244
 
245
245
  /**
246
246
  * Fan-out listeners notified on EVERY access-token change on this instance:
247
- * explicit `setTokens`, `clearTokens`, an AuthManager-owned refresh, and the
247
+ * explicit `setTokens`, `clearTokens`, a refresh-handler rotation, and the
248
248
  * internal 401-driven clear. This is a Set so multiple independent observers
249
249
  * can mirror token state without clobbering each other.
250
250
  *
@@ -527,9 +527,9 @@ export class HttpService {
527
527
 
528
528
  // Handle response
529
529
  if (!response.ok) {
530
- // On 401, delegate refresh to AuthManager and retry once before
531
- // giving up. HttpService deliberately does not know any session
532
- // routes; the AuthManager is the single session authority.
530
+ // On 401, delegate to the installed auth refresh handler and retry
531
+ // once before giving up. HttpService deliberately does not know any
532
+ // session routes; the refresh handler owns session rotation.
533
533
  if (response.status === 401 && !config._isAuthRetry && !config.skipAuth) {
534
534
  const refreshed = await this.refreshAccessToken('response-401');
535
535
  if (refreshed) {
@@ -1045,7 +1045,7 @@ export class HttpService {
1045
1045
  this.tokenStore.setTokens(newToken);
1046
1046
  this.notifyTokenChange();
1047
1047
  }
1048
- this.logger.debug('Token refreshed via AuthManager');
1048
+ this.logger.debug('Token refreshed via the auth refresh handler');
1049
1049
  return newToken;
1050
1050
  })
1051
1051
  .catch((error) => {