@oxyhq/core 7.1.0 → 8.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/README.md +48 -24
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +6 -6
- package/dist/cjs/i18n/locales/en-US.json +44 -1
- package/dist/cjs/i18n/locales/es-ES.json +44 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +45 -2
- package/dist/cjs/i18n/locales/locales/es-ES.json +45 -2
- package/dist/cjs/index.js +19 -16
- package/dist/cjs/server/index.js +1 -7
- package/dist/cjs/session/accountDialogController.js +88 -3
- package/dist/cjs/session/accountProjection.js +1 -1
- package/dist/cjs/session/projectSessionState.js +1 -1
- package/dist/cjs/session/sessionClientHost.js +1 -2
- package/dist/cjs/utils/accountUtils.js +1 -1
- package/dist/cjs/utils/oauthPkce.js +142 -0
- package/dist/cjs/utils/platform.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +6 -6
- package/dist/esm/i18n/locales/en-US.json +44 -1
- package/dist/esm/i18n/locales/es-ES.json +44 -1
- package/dist/esm/i18n/locales/locales/en-US.json +45 -2
- package/dist/esm/i18n/locales/locales/es-ES.json +45 -2
- package/dist/esm/index.js +11 -13
- package/dist/esm/server/index.js +0 -5
- package/dist/esm/session/accountDialogController.js +88 -3
- package/dist/esm/session/accountProjection.js +1 -1
- package/dist/esm/session/projectSessionState.js +1 -1
- package/dist/esm/session/sessionClientHost.js +1 -2
- package/dist/esm/utils/accountUtils.js +1 -1
- package/dist/esm/utils/oauthPkce.js +135 -0
- package/dist/esm/utils/platform.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/mixins/OxyServices.accounts.d.ts +13 -3
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +4 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +3 -2
- package/dist/types/models/interfaces.d.ts +4 -4
- package/dist/types/server/index.d.ts +0 -1
- package/dist/types/session/accountDialogController.d.ts +28 -1
- package/dist/types/session/accountProjection.d.ts +1 -1
- package/dist/types/session/projectSessionState.d.ts +1 -1
- package/dist/types/session/refresh.d.ts +4 -8
- package/dist/types/session/sessionClientHost.d.ts +1 -2
- package/dist/types/utils/accountUtils.d.ts +1 -1
- package/dist/types/utils/oauthPkce.d.ts +74 -0
- package/dist/types/utils/platform.d.ts +1 -1
- package/package.json +3 -3
- package/src/HttpService.ts +6 -6
- package/src/i18n/locales/en-US.json +45 -2
- package/src/i18n/locales/es-ES.json +45 -2
- package/src/index.ts +23 -16
- package/src/mixins/OxyServices.accounts.ts +12 -0
- package/src/mixins/OxyServices.connectedApps.ts +4 -0
- package/src/mixins/OxyServices.devices.ts +6 -5
- package/src/mixins/__tests__/accounts.test.ts +1 -1
- package/src/models/interfaces.ts +7 -5
- package/src/server/index.ts +0 -6
- package/src/session/__tests__/accountDialogController.test.ts +123 -0
- package/src/session/accountDialogController.ts +90 -3
- package/src/session/accountProjection.ts +1 -1
- package/src/session/projectSessionState.ts +1 -1
- package/src/session/refresh.ts +4 -8
- package/src/session/sessionClientHost.ts +1 -2
- package/src/utils/__tests__/coldBoot.test.ts +55 -65
- package/src/utils/__tests__/oauthPkce.test.ts +154 -0
- package/src/utils/accountUtils.ts +1 -1
- package/src/utils/oauthPkce.ts +189 -0
- package/src/utils/platform.ts +1 -1
- package/dist/cjs/utils/ssoBounce.js +0 -24
- package/dist/esm/utils/ssoBounce.js +0 -21
- package/dist/types/utils/ssoBounce.d.ts +0 -21
- package/src/utils/ssoBounce.ts +0 -22
package/dist/cjs/HttpService.js
CHANGED
|
@@ -56,7 +56,7 @@ const CSRF_FETCH_RETRY_DELAY_MS = 500;
|
|
|
56
56
|
/**
|
|
57
57
|
* Cooldown (ms) applied after a failed access-token refresh before another
|
|
58
58
|
* refresh is attempted. Prevents a refresh storm (and server hammering) when
|
|
59
|
-
* the
|
|
59
|
+
* the auth refresh handler is failing — every in-flight request that
|
|
60
60
|
* hits a 401 would otherwise trigger its own refresh.
|
|
61
61
|
*/
|
|
62
62
|
const TOKEN_REFRESH_COOLDOWN_MS = 15000;
|
|
@@ -143,7 +143,7 @@ class HttpService {
|
|
|
143
143
|
this.cacheSizeWarningSilentUntil = 0;
|
|
144
144
|
/**
|
|
145
145
|
* Fan-out listeners notified on EVERY access-token change on this instance:
|
|
146
|
-
* explicit `setTokens`, `clearTokens`,
|
|
146
|
+
* explicit `setTokens`, `clearTokens`, a refresh-handler rotation, and the
|
|
147
147
|
* internal 401-driven clear. This is a Set so multiple independent observers
|
|
148
148
|
* can mirror token state without clobbering each other.
|
|
149
149
|
*
|
|
@@ -361,9 +361,9 @@ class HttpService {
|
|
|
361
361
|
clearTimeout(timeoutId);
|
|
362
362
|
// Handle response
|
|
363
363
|
if (!response.ok) {
|
|
364
|
-
// On 401, delegate refresh
|
|
365
|
-
// giving up. HttpService deliberately does not know any
|
|
366
|
-
// routes; the
|
|
364
|
+
// On 401, delegate to the installed auth refresh handler and retry
|
|
365
|
+
// once before giving up. HttpService deliberately does not know any
|
|
366
|
+
// session routes; the refresh handler owns session rotation.
|
|
367
367
|
if (response.status === 401 && !config._isAuthRetry && !config.skipAuth) {
|
|
368
368
|
const refreshed = await this.refreshAccessToken('response-401');
|
|
369
369
|
if (refreshed) {
|
|
@@ -823,7 +823,7 @@ class HttpService {
|
|
|
823
823
|
this.tokenStore.setTokens(newToken);
|
|
824
824
|
this.notifyTokenChange();
|
|
825
825
|
}
|
|
826
|
-
this.logger.debug('Token refreshed via
|
|
826
|
+
this.logger.debug('Token refreshed via the auth refresh handler');
|
|
827
827
|
return newToken;
|
|
828
828
|
})
|
|
829
829
|
.catch((error) => {
|
|
@@ -384,7 +384,11 @@
|
|
|
384
384
|
"subtitle": "Add another account to switch between them quickly"
|
|
385
385
|
},
|
|
386
386
|
"label": "Account switcher",
|
|
387
|
-
"searchPlaceholder": "Search accounts"
|
|
387
|
+
"searchPlaceholder": "Search accounts",
|
|
388
|
+
"scanTitle": "Scan to sign in",
|
|
389
|
+
"scanSubtitle": "Open the Oxy app on your phone and scan this code",
|
|
390
|
+
"scanWithOxy": "Scan with the Oxy app to continue",
|
|
391
|
+
"scanQr": "Scan QR code"
|
|
388
392
|
},
|
|
389
393
|
"reputation": {
|
|
390
394
|
"faq": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Early Adopter",
|
|
1906
1910
|
"earlyAdopterDesc": "Been part of the community from the start"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continue to {{app}}",
|
|
1915
|
+
"subtitle": "Use your Oxy account to sign in to {{app}}. Review what this connection means before you continue.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Who is requesting access",
|
|
1918
|
+
"official": "Official Oxy application",
|
|
1919
|
+
"developer": "Published by {{developer}}",
|
|
1920
|
+
"thirdParty": "Third-party application"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permissions requested",
|
|
1924
|
+
"basic": "Sign you in and read your basic profile"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirm your identity",
|
|
1928
|
+
"profile": "Read your basic profile",
|
|
1929
|
+
"email": "Read your email address",
|
|
1930
|
+
"offlineAccess": "Keep you signed in when you're away",
|
|
1931
|
+
"userRead": "Read your basic profile",
|
|
1932
|
+
"filesRead": "Read your files",
|
|
1933
|
+
"filesWrite": "Upload and modify your files",
|
|
1934
|
+
"filesDelete": "Delete your files",
|
|
1935
|
+
"webhooksReceive": "Receive webhooks",
|
|
1936
|
+
"chatCompletions": "Use AI chat on your behalf",
|
|
1937
|
+
"modelsRead": "List available AI models",
|
|
1938
|
+
"federationWrite": "Act across federated services"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Signing in as"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Website",
|
|
1945
|
+
"privacy": "Privacy Policy",
|
|
1946
|
+
"terms": "Terms of Service"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continue to {{app}}",
|
|
1949
|
+
"deny": "Cancel",
|
|
1950
|
+
"disclaimer": "By continuing, {{app}} will be able to sign in with your Oxy account. You can manage connected apps anytime in your Oxy account settings."
|
|
1908
1951
|
}
|
|
1909
1952
|
}
|
|
@@ -581,7 +581,11 @@
|
|
|
581
581
|
"subtitle": "Añade otra cuenta para cambiar rápidamente entre ellas"
|
|
582
582
|
},
|
|
583
583
|
"label": "Selector de cuentas",
|
|
584
|
-
"searchPlaceholder": "Buscar cuentas"
|
|
584
|
+
"searchPlaceholder": "Buscar cuentas",
|
|
585
|
+
"scanTitle": "Escanea para iniciar sesión",
|
|
586
|
+
"scanSubtitle": "Abre la app de Oxy en tu móvil y escanea este código",
|
|
587
|
+
"scanWithOxy": "Escanea con la app de Oxy para continuar",
|
|
588
|
+
"scanQr": "Escanear código QR"
|
|
585
589
|
},
|
|
586
590
|
"feedback": {
|
|
587
591
|
"type": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Pionero",
|
|
1906
1910
|
"earlyAdopterDesc": "Has formado parte de la comunidad desde el principio"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continuar a {{app}}",
|
|
1915
|
+
"subtitle": "Usa tu cuenta de Oxy para iniciar sesión en {{app}}. Revisa qué implica esta conexión antes de continuar.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Quién solicita acceso",
|
|
1918
|
+
"official": "Aplicación oficial de Oxy",
|
|
1919
|
+
"developer": "Publicada por {{developer}}",
|
|
1920
|
+
"thirdParty": "Aplicación de terceros"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permisos solicitados",
|
|
1924
|
+
"basic": "Iniciar sesión y leer tu perfil básico"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirmar tu identidad",
|
|
1928
|
+
"profile": "Leer tu perfil básico",
|
|
1929
|
+
"email": "Leer tu dirección de correo",
|
|
1930
|
+
"offlineAccess": "Mantener tu sesión iniciada cuando no estés",
|
|
1931
|
+
"userRead": "Leer tu perfil básico",
|
|
1932
|
+
"filesRead": "Leer tus archivos",
|
|
1933
|
+
"filesWrite": "Subir y modificar tus archivos",
|
|
1934
|
+
"filesDelete": "Eliminar tus archivos",
|
|
1935
|
+
"webhooksReceive": "Recibir webhooks",
|
|
1936
|
+
"chatCompletions": "Usar el chat con IA en tu nombre",
|
|
1937
|
+
"modelsRead": "Ver los modelos de IA disponibles",
|
|
1938
|
+
"federationWrite": "Actuar en servicios federados"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Iniciando sesión como"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Sitio web",
|
|
1945
|
+
"privacy": "Política de privacidad",
|
|
1946
|
+
"terms": "Términos del servicio"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continuar a {{app}}",
|
|
1949
|
+
"deny": "Cancelar",
|
|
1950
|
+
"disclaimer": "Al continuar, {{app}} podrá iniciar sesión con tu cuenta de Oxy. Puedes gestionar las apps conectadas cuando quieras en los ajustes de tu cuenta de Oxy."
|
|
1908
1951
|
}
|
|
1909
1952
|
}
|
|
@@ -384,7 +384,11 @@
|
|
|
384
384
|
"subtitle": "Add another account to switch between them quickly"
|
|
385
385
|
},
|
|
386
386
|
"label": "Account switcher",
|
|
387
|
-
"searchPlaceholder": "Search accounts"
|
|
387
|
+
"searchPlaceholder": "Search accounts",
|
|
388
|
+
"scanTitle": "Scan to sign in",
|
|
389
|
+
"scanSubtitle": "Open the Oxy app on your phone and scan this code",
|
|
390
|
+
"scanWithOxy": "Scan with the Oxy app to continue",
|
|
391
|
+
"scanQr": "Scan QR code"
|
|
388
392
|
},
|
|
389
393
|
"reputation": {
|
|
390
394
|
"faq": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Early Adopter",
|
|
1906
1910
|
"earlyAdopterDesc": "Been part of the community from the start"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continue to {{app}}",
|
|
1915
|
+
"subtitle": "Use your Oxy account to sign in to {{app}}. Review what this connection means before you continue.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Who is requesting access",
|
|
1918
|
+
"official": "Official Oxy application",
|
|
1919
|
+
"developer": "Published by {{developer}}",
|
|
1920
|
+
"thirdParty": "Third-party application"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permissions requested",
|
|
1924
|
+
"basic": "Sign you in and read your basic profile"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirm your identity",
|
|
1928
|
+
"profile": "Read your basic profile",
|
|
1929
|
+
"email": "Read your email address",
|
|
1930
|
+
"offlineAccess": "Keep you signed in when you're away",
|
|
1931
|
+
"userRead": "Read your basic profile",
|
|
1932
|
+
"filesRead": "Read your files",
|
|
1933
|
+
"filesWrite": "Upload and modify your files",
|
|
1934
|
+
"filesDelete": "Delete your files",
|
|
1935
|
+
"webhooksReceive": "Receive webhooks",
|
|
1936
|
+
"chatCompletions": "Use AI chat on your behalf",
|
|
1937
|
+
"modelsRead": "List available AI models",
|
|
1938
|
+
"federationWrite": "Act across federated services"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Signing in as"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Website",
|
|
1945
|
+
"privacy": "Privacy Policy",
|
|
1946
|
+
"terms": "Terms of Service"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continue to {{app}}",
|
|
1949
|
+
"deny": "Cancel",
|
|
1950
|
+
"disclaimer": "By continuing, {{app}} will be able to sign in with your Oxy account. You can manage connected apps anytime in your Oxy account settings."
|
|
1908
1951
|
}
|
|
1909
|
-
}
|
|
1952
|
+
}
|
|
@@ -581,7 +581,11 @@
|
|
|
581
581
|
"subtitle": "Añade otra cuenta para cambiar rápidamente entre ellas"
|
|
582
582
|
},
|
|
583
583
|
"label": "Selector de cuentas",
|
|
584
|
-
"searchPlaceholder": "Buscar cuentas"
|
|
584
|
+
"searchPlaceholder": "Buscar cuentas",
|
|
585
|
+
"scanTitle": "Escanea para iniciar sesión",
|
|
586
|
+
"scanSubtitle": "Abre la app de Oxy en tu móvil y escanea este código",
|
|
587
|
+
"scanWithOxy": "Escanea con la app de Oxy para continuar",
|
|
588
|
+
"scanQr": "Escanear código QR"
|
|
585
589
|
},
|
|
586
590
|
"feedback": {
|
|
587
591
|
"type": {
|
|
@@ -1905,5 +1909,44 @@
|
|
|
1905
1909
|
"earlyAdopter": "Pionero",
|
|
1906
1910
|
"earlyAdopterDesc": "Has formado parte de la comunidad desde el principio"
|
|
1907
1911
|
}
|
|
1912
|
+
},
|
|
1913
|
+
"consent": {
|
|
1914
|
+
"title": "Continuar a {{app}}",
|
|
1915
|
+
"subtitle": "Usa tu cuenta de Oxy para iniciar sesión en {{app}}. Revisa qué implica esta conexión antes de continuar.",
|
|
1916
|
+
"provenance": {
|
|
1917
|
+
"title": "Quién solicita acceso",
|
|
1918
|
+
"official": "Aplicación oficial de Oxy",
|
|
1919
|
+
"developer": "Publicada por {{developer}}",
|
|
1920
|
+
"thirdParty": "Aplicación de terceros"
|
|
1921
|
+
},
|
|
1922
|
+
"permissions": {
|
|
1923
|
+
"title": "Permisos solicitados",
|
|
1924
|
+
"basic": "Iniciar sesión y leer tu perfil básico"
|
|
1925
|
+
},
|
|
1926
|
+
"scopes": {
|
|
1927
|
+
"openid": "Confirmar tu identidad",
|
|
1928
|
+
"profile": "Leer tu perfil básico",
|
|
1929
|
+
"email": "Leer tu dirección de correo",
|
|
1930
|
+
"offlineAccess": "Mantener tu sesión iniciada cuando no estés",
|
|
1931
|
+
"userRead": "Leer tu perfil básico",
|
|
1932
|
+
"filesRead": "Leer tus archivos",
|
|
1933
|
+
"filesWrite": "Subir y modificar tus archivos",
|
|
1934
|
+
"filesDelete": "Eliminar tus archivos",
|
|
1935
|
+
"webhooksReceive": "Recibir webhooks",
|
|
1936
|
+
"chatCompletions": "Usar el chat con IA en tu nombre",
|
|
1937
|
+
"modelsRead": "Ver los modelos de IA disponibles",
|
|
1938
|
+
"federationWrite": "Actuar en servicios federados"
|
|
1939
|
+
},
|
|
1940
|
+
"account": {
|
|
1941
|
+
"title": "Iniciando sesión como"
|
|
1942
|
+
},
|
|
1943
|
+
"links": {
|
|
1944
|
+
"website": "Sitio web",
|
|
1945
|
+
"privacy": "Política de privacidad",
|
|
1946
|
+
"terms": "Términos del servicio"
|
|
1947
|
+
},
|
|
1948
|
+
"allow": "Continuar a {{app}}",
|
|
1949
|
+
"deny": "Cancelar",
|
|
1950
|
+
"disclaimer": "Al continuar, {{app}} podrá iniciar sesión con tu cuenta de Oxy. Puedes gestionar las apps conectadas cuando quieras en los ajustes de tu cuenta de Oxy."
|
|
1908
1951
|
}
|
|
1909
|
-
}
|
|
1952
|
+
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.lightenColor = exports.darkenColor = exports.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
22
|
exports.isValidPassword = exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = void 0;
|
|
23
|
-
exports.
|
|
24
|
-
exports.packageInfo = exports.BOOT_STATE_SESSION_KEY = exports.BOOT_FRAGMENT_PARAM = exports.hashHasBootFragment = exports.parseDeviceBootFragment = exports.consumeDeviceBootReturn = exports.BOOT_ATTEMPTED_KEY = exports.isSameApex = exports.createBrowserColdBootDom = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.DEVICE_TOKEN_STORAGE_KEY = void 0;
|
|
23
|
+
exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidDisplayName = void 0;
|
|
24
|
+
exports.packageInfo = exports.BOOT_STATE_SESSION_KEY = exports.BOOT_FRAGMENT_PARAM = exports.hashHasBootFragment = exports.parseDeviceBootFragment = exports.consumeDeviceBootReturn = exports.BOOT_ATTEMPTED_KEY = exports.isSameApex = exports.createBrowserColdBootDom = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.DEVICE_TOKEN_STORAGE_KEY = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = void 0;
|
|
25
25
|
// Ensure crypto polyfills are loaded before anything else
|
|
26
26
|
require("./crypto/polyfill");
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -256,33 +256,36 @@ Object.defineProperty(exports, "getAccountColor", { enumerable: true, get: funct
|
|
|
256
256
|
// ---------------------------------------------------------------------------
|
|
257
257
|
// Registrable-domain + central-IdP-apex helpers.
|
|
258
258
|
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// `packages/api/src/utils/sameSite.ts` for same-site origin checks, and
|
|
264
|
-
// `CENTRAL_IDP_APEX` by `server/cors.ts`'s `createOxyCors` (auto-allows
|
|
265
|
-
// `*.oxy.so`). `SSO_CALLBACK_PATH` has no remaining importer outside this
|
|
266
|
-
// module as of wave 2 — kept exported for now rather than removed here (an
|
|
267
|
-
// export deletion is a logic change, out of scope for a comment sweep); flag
|
|
268
|
-
// for a follow-up dead-export cleanup pass.
|
|
259
|
+
// `registrableApex` (eTLD+1) is consumed via the `@oxyhq/core/server`
|
|
260
|
+
// re-export by `packages/api/src/utils/sameSite.ts` for same-site origin
|
|
261
|
+
// checks; `CENTRAL_IDP_APEX` by `server/cors.ts`'s `createOxyCors` (auto-allows
|
|
262
|
+
// `*.oxy.so`).
|
|
269
263
|
// ---------------------------------------------------------------------------
|
|
270
264
|
var registrableApex_1 = require("./utils/registrableApex");
|
|
271
265
|
Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
|
|
272
266
|
var authWebUrl_1 = require("./utils/authWebUrl");
|
|
273
267
|
Object.defineProperty(exports, "CENTRAL_IDP_APEX", { enumerable: true, get: function () { return authWebUrl_1.CENTRAL_IDP_APEX; } });
|
|
274
|
-
var ssoBounce_1 = require("./utils/ssoBounce");
|
|
275
|
-
Object.defineProperty(exports, "SSO_CALLBACK_PATH", { enumerable: true, get: function () { return ssoBounce_1.SSO_CALLBACK_PATH; } });
|
|
276
268
|
var coldBoot_1 = require("./utils/coldBoot");
|
|
277
269
|
Object.defineProperty(exports, "runColdBoot", { enumerable: true, get: function () { return coldBoot_1.runColdBoot; } });
|
|
278
270
|
// ---------------------------------------------------------------------------
|
|
271
|
+
// OAuth 2.0 Authorization Code + PKCE helpers ("Sign in with Oxy" third party).
|
|
272
|
+
// Standard OAuth against auth.oxy.so/authorize — no FedCM/cookies/SSO bounce.
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
var oauthPkce_1 = require("./utils/oauthPkce");
|
|
275
|
+
Object.defineProperty(exports, "buildOAuthAuthorizeUrl", { enumerable: true, get: function () { return oauthPkce_1.buildOAuthAuthorizeUrl; } });
|
|
276
|
+
Object.defineProperty(exports, "computeCodeChallenge", { enumerable: true, get: function () { return oauthPkce_1.computeCodeChallenge; } });
|
|
277
|
+
Object.defineProperty(exports, "generateOAuthState", { enumerable: true, get: function () { return oauthPkce_1.generateOAuthState; } });
|
|
278
|
+
Object.defineProperty(exports, "generatePkcePair", { enumerable: true, get: function () { return oauthPkce_1.generatePkcePair; } });
|
|
279
|
+
Object.defineProperty(exports, "DEFAULT_OAUTH_SCOPE", { enumerable: true, get: function () { return oauthPkce_1.DEFAULT_OAUTH_SCOPE; } });
|
|
280
|
+
Object.defineProperty(exports, "OXY_AUTHORIZE_URL", { enumerable: true, get: function () { return oauthPkce_1.OXY_AUTHORIZE_URL; } });
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
279
282
|
// Session sync (device-scoped multi-account session client)
|
|
280
283
|
// ---------------------------------------------------------------------------
|
|
281
284
|
var SessionClient_1 = require("./session/SessionClient");
|
|
282
285
|
Object.defineProperty(exports, "SessionClient", { enumerable: true, get: function () { return SessionClient_1.SessionClient; } });
|
|
283
286
|
// Shared SessionClient integration layer: the host adapter, the pure
|
|
284
287
|
// DeviceSessionState projection helpers, and the client factory are defined
|
|
285
|
-
// ONCE here so `@oxyhq/services`
|
|
288
|
+
// ONCE here so every `@oxyhq/services` platform variant reuses them instead of
|
|
286
289
|
// duplicating a local copy. Each consumer supplies its own `TokenTransport`
|
|
287
290
|
// (native vs. web mint strategies differ) to `createSessionClient`.
|
|
288
291
|
var sessionClientHost_1 = require("./session/sessionClientHost");
|
|
@@ -297,7 +300,7 @@ Object.defineProperty(exports, "accountIdsOf", { enumerable: true, get: function
|
|
|
297
300
|
// Unified account-list projection (THE single source of truth for the account
|
|
298
301
|
// chooser: device sign-ins ∪ account graph, deduped by accountId). Pure +
|
|
299
302
|
// I/O-free — the caller hydrates profiles via `getUsersByIds`. Shared by
|
|
300
|
-
// `@oxyhq/services
|
|
303
|
+
// `@oxyhq/services` and auth.oxy.so so the list can't diverge.
|
|
301
304
|
var accountProjection_1 = require("./session/accountProjection");
|
|
302
305
|
Object.defineProperty(exports, "projectSwitchableAccounts", { enumerable: true, get: function () { return accountProjection_1.projectSwitchableAccounts; } });
|
|
303
306
|
Object.defineProperty(exports, "switchableAccountIds", { enumerable: true, get: function () { return accountProjection_1.switchableAccountIds; } });
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.
|
|
19
|
+
exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
20
20
|
var auth_1 = require("./auth");
|
|
21
21
|
Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
|
|
22
22
|
Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
|
|
@@ -52,9 +52,3 @@ Object.defineProperty(exports, "verifySecret", { enumerable: true, get: function
|
|
|
52
52
|
// lets `@oxyhq/api` derive `auth.<apex>` without duplicating PSL logic.
|
|
53
53
|
var registrableApex_1 = require("../utils/registrableApex");
|
|
54
54
|
Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
|
|
55
|
-
// The single RP callback path the IdP redirects back to. A pure wire-contract
|
|
56
|
-
// constant (no browser deps at module top level), re-used server-side so the
|
|
57
|
-
// `/sso/establish-token` `return_to` cannot drift from what `/sso/establish`
|
|
58
|
-
// validates.
|
|
59
|
-
var ssoBounce_1 = require("../utils/ssoBounce");
|
|
60
|
-
Object.defineProperty(exports, "SSO_CALLBACK_PATH", { enumerable: true, get: function () { return ssoBounce_1.SSO_CALLBACK_PATH; } });
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* A framework-agnostic state machine + subscribe/getSnapshot store (the same
|
|
6
6
|
* pattern {@link SessionClient} uses — no React, no RN) that both
|
|
7
|
-
*
|
|
7
|
+
* every `OxyProvider` platform variant (Expo/RN and RN-Web)
|
|
8
8
|
* bind to via `useSyncExternalStore`, so the account chooser is ONE
|
|
9
9
|
* implementation across the ecosystem instead of the five drifting copies it
|
|
10
10
|
* replaces.
|
|
@@ -60,6 +60,9 @@ class AccountDialogController {
|
|
|
60
60
|
this.pollTimer = null;
|
|
61
61
|
// --- Store plumbing ---
|
|
62
62
|
this.unsubscribeSession = null;
|
|
63
|
+
this.unsubscribeTokens = null;
|
|
64
|
+
/** Last-observed SDK auth readiness (a planted bearer). Drives the fetch edge. */
|
|
65
|
+
this.authed = false;
|
|
63
66
|
this.started = false;
|
|
64
67
|
this.refreshSeq = 0;
|
|
65
68
|
this.oxyServices = options.oxyServices;
|
|
@@ -99,13 +102,28 @@ class AccountDialogController {
|
|
|
99
102
|
if (this.started)
|
|
100
103
|
return;
|
|
101
104
|
this.started = true;
|
|
105
|
+
this.authed = this.isAuthenticated();
|
|
102
106
|
this.unsubscribeSession = this.sessionClient.subscribe(() => {
|
|
103
107
|
// A device-state change (switch / sign-out / sibling sign-in) can add or
|
|
104
|
-
// remove accounts — re-project immediately,
|
|
105
|
-
// account ids appeared.
|
|
108
|
+
// remove accounts — re-project immediately, refetch profiles when new
|
|
109
|
+
// account ids appeared, and reconcile the auth-readiness edge.
|
|
106
110
|
this.emit();
|
|
107
111
|
void this.ensureProfiles();
|
|
112
|
+
this.reconcileAuth();
|
|
108
113
|
});
|
|
114
|
+
// The access token is planted AFTER `SessionClient.applyState` fires its
|
|
115
|
+
// subscription (`applySync` calls `setTokens` only once `applyState`/notify
|
|
116
|
+
// has returned; `ensureActiveToken` plants it async later), so the
|
|
117
|
+
// device-state subscription alone cannot observe the signed-out → signed-in
|
|
118
|
+
// edge. Observe the SDK-canonical readiness signal directly — a change to
|
|
119
|
+
// `oxyServices.getAccessToken()`, the `hasAccessToken` term of
|
|
120
|
+
// `OxyContext.canUsePrivateApi`.
|
|
121
|
+
this.unsubscribeTokens = this.oxyServices.onTokensChanged(() => {
|
|
122
|
+
this.reconcileAuth();
|
|
123
|
+
});
|
|
124
|
+
// Initial projection is device-only. `refresh()` fetches the graph IFF a
|
|
125
|
+
// bearer is already planted (warm start); when signed out (cold boot before
|
|
126
|
+
// restore) it re-projects from device state and makes NO private call.
|
|
109
127
|
void this.refresh();
|
|
110
128
|
}
|
|
111
129
|
/**
|
|
@@ -118,10 +136,58 @@ class AccountDialogController {
|
|
|
118
136
|
this.unsubscribeSession();
|
|
119
137
|
this.unsubscribeSession = null;
|
|
120
138
|
}
|
|
139
|
+
if (this.unsubscribeTokens) {
|
|
140
|
+
this.unsubscribeTokens();
|
|
141
|
+
this.unsubscribeTokens = null;
|
|
142
|
+
}
|
|
121
143
|
this.clearPollTimer();
|
|
122
144
|
this.listeners.clear();
|
|
123
145
|
}
|
|
124
146
|
// =========================================================================
|
|
147
|
+
// Auth readiness (SDK-canonical — mirrors OxyContext.canUsePrivateApi)
|
|
148
|
+
// =========================================================================
|
|
149
|
+
/**
|
|
150
|
+
* Whether a PRIVATE endpoint may be called right now. Mirrors the
|
|
151
|
+
* `hasAccessToken` term of `OxyContext.canUsePrivateApi`
|
|
152
|
+
* (`authResolved && isAuthenticated && tokenReady && hasAccessToken`, where
|
|
153
|
+
* `hasAccessToken = Boolean(oxyServices.getAccessToken())`): a planted bearer
|
|
154
|
+
* is the only term that decides whether a request carries auth — the other
|
|
155
|
+
* three are provider render-lifecycle gates with no headless equivalent.
|
|
156
|
+
*
|
|
157
|
+
* `listAccounts()` (`GET /accounts`) and `getUsersByIds()`
|
|
158
|
+
* (`POST /users/by-ids`) are private; calling either before cold-boot restore
|
|
159
|
+
* plants the token 401s → `HttpService` clears the bearer + emits
|
|
160
|
+
* `onTokensChanged(null)` → the app signs out. Every graph/profile fetch gates
|
|
161
|
+
* on this.
|
|
162
|
+
*/
|
|
163
|
+
isAuthenticated() {
|
|
164
|
+
return Boolean(this.oxyServices.getAccessToken());
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Reconcile the account graph against the current auth-readiness edge. On the
|
|
168
|
+
* signed-out → signed-in edge fetch the graph ONCE; on signed-in → signed-out
|
|
169
|
+
* drop it and re-project device-only. A no-op when readiness is unchanged, so
|
|
170
|
+
* a burst of token events / device pushes cannot restart the fetch — and a
|
|
171
|
+
* failed `listAccounts()` never flips the edge, so it cannot re-trigger itself
|
|
172
|
+
* (no retry storm).
|
|
173
|
+
*/
|
|
174
|
+
reconcileAuth() {
|
|
175
|
+
const authed = this.isAuthenticated();
|
|
176
|
+
if (authed === this.authed)
|
|
177
|
+
return;
|
|
178
|
+
this.authed = authed;
|
|
179
|
+
if (authed) {
|
|
180
|
+
void this.refresh();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Signed out: the graph is no longer fetchable/switchable — drop it and
|
|
184
|
+
// re-project from the device session set alone.
|
|
185
|
+
this.graph = [];
|
|
186
|
+
this.error = null;
|
|
187
|
+
this.loading = false;
|
|
188
|
+
this.emit();
|
|
189
|
+
}
|
|
190
|
+
// =========================================================================
|
|
125
191
|
// View actions
|
|
126
192
|
// =========================================================================
|
|
127
193
|
/** Set the dialog view directly. */
|
|
@@ -150,6 +216,17 @@ class AccountDialogController {
|
|
|
150
216
|
*/
|
|
151
217
|
async refresh() {
|
|
152
218
|
const seq = ++this.refreshSeq;
|
|
219
|
+
// Never hit the private `listAccounts()` while signed out: at cold boot the
|
|
220
|
+
// bearer is not planted yet, so the call 401s → `HttpService` clears the
|
|
221
|
+
// token and signs the user out. Re-project from the device session set alone
|
|
222
|
+
// (`projectSwitchableAccounts` works from `SessionClient` state) and stop.
|
|
223
|
+
if (!this.isAuthenticated()) {
|
|
224
|
+
this.graph = [];
|
|
225
|
+
this.loading = false;
|
|
226
|
+
this.error = null;
|
|
227
|
+
this.emit();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
153
230
|
const hadAccounts = this.snapshot.accounts.length > 0;
|
|
154
231
|
this.loading = !hadAccounts;
|
|
155
232
|
this.error = null;
|
|
@@ -179,6 +256,9 @@ class AccountDialogController {
|
|
|
179
256
|
* subscription so a newly-added device account gets a name/avatar.
|
|
180
257
|
*/
|
|
181
258
|
async ensureProfiles() {
|
|
259
|
+
// `getUsersByIds` is private — skip the whole path while signed out.
|
|
260
|
+
if (!this.isAuthenticated())
|
|
261
|
+
return;
|
|
182
262
|
const ids = (0, accountProjection_1.switchableAccountIds)(this.sessionClient.getState(), this.graph);
|
|
183
263
|
if (ids.every((id) => this.profilesById.has(id)))
|
|
184
264
|
return;
|
|
@@ -186,6 +266,11 @@ class AccountDialogController {
|
|
|
186
266
|
this.emit();
|
|
187
267
|
}
|
|
188
268
|
async loadProfiles(seq) {
|
|
269
|
+
// `getUsersByIds` (`POST /users/by-ids`) is a private call — never issue it
|
|
270
|
+
// while signed out (the 401 → sign-out cascade). Callers already gate; this
|
|
271
|
+
// guards the network chokepoint too (e.g. the token was cleared mid-refresh).
|
|
272
|
+
if (!this.isAuthenticated())
|
|
273
|
+
return;
|
|
189
274
|
const ids = (0, accountProjection_1.switchableAccountIds)(this.sessionClient.getState(), this.graph);
|
|
190
275
|
if (ids.length === 0)
|
|
191
276
|
return;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* merging the device's server-authoritative session set (`DeviceSessionState`
|
|
7
7
|
* from {@link SessionClient}) with the caller's account graph (`AccountNode[]`
|
|
8
8
|
* from `oxyServices.listAccounts()`), deduped by `accountId`. This lives in
|
|
9
|
-
* `@oxyhq/core` so `@oxyhq/services`
|
|
9
|
+
* `@oxyhq/core` so every `@oxyhq/services` platform variant — and
|
|
10
10
|
* `auth.oxy.so` — all render the SAME list from the SAME logic and cannot
|
|
11
11
|
* diverge.
|
|
12
12
|
*
|
|
@@ -7,7 +7,7 @@ exports.accountIdsOf = accountIdsOf;
|
|
|
7
7
|
/**
|
|
8
8
|
* Pure projection helpers: `DeviceSessionState` (the device-scoped
|
|
9
9
|
* multi-account session-sync state produced by `SessionClient`) -> the
|
|
10
|
-
* shapes
|
|
10
|
+
* shapes `@oxyhq/services` consumers render today
|
|
11
11
|
* (`ClientSession[]`, an active session id, an active `User`).
|
|
12
12
|
*
|
|
13
13
|
* No I/O. The caller fetches profiles via
|
|
@@ -7,8 +7,7 @@ exports.createSessionClientHost = createSessionClientHost;
|
|
|
7
7
|
* `SessionClient` is host-agnostic: it only needs a REST + token surface.
|
|
8
8
|
* `OxyServices` already exposes all of that except `getCurrentAccountId`,
|
|
9
9
|
* which has no direct equivalent — the adapter holds a mutable ref set by
|
|
10
|
-
* the caller (`OxyContext` in `@oxyhq/services
|
|
11
|
-
* `@oxyhq/auth`) via `setCurrentAccountId`.
|
|
10
|
+
* the caller (`OxyContext` in `@oxyhq/services`) via `setCurrentAccountId`.
|
|
12
11
|
*
|
|
13
12
|
* Shared here (rather than duplicated per consumer) because it is entirely
|
|
14
13
|
* platform-agnostic: every method it calls exists identically on
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Shared account types and pure helper functions.
|
|
4
|
-
* Used by
|
|
4
|
+
* Used by the @oxyhq/services account stores (Expo/RN and RN-Web).
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.getAccountColor = exports.createQuickAccount = exports.buildAccountsArray = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.formatPublicKeyHandle = void 0;
|