@oxyhq/core 3.4.4 → 3.4.6
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/OxyServices.base.js +39 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/utils/ssoBounce.js +32 -0
- package/dist/cjs/utils/ssoReturn.js +4 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.base.js +39 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/ssoBounce.js +30 -0
- package/dist/esm/utils/ssoReturn.js +5 -2
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/OxyServices.base.d.ts +14 -0
- package/dist/types/OxyServices.d.ts +2 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
- package/dist/types/mixins/OxyServices.applications.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +1 -0
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.language.d.ts +1 -0
- package/dist/types/mixins/OxyServices.location.d.ts +1 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -0
- package/dist/types/mixins/OxyServices.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.silent.d.ts +1 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +1 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
- package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -0
- package/dist/types/utils/ssoBounce.d.ts +23 -0
- package/package.json +1 -1
- package/src/HttpService.ts +1 -1
- package/src/OxyServices.base.ts +50 -1
- package/src/OxyServices.ts +3 -1
- package/src/__tests__/linkedClient.test.ts +64 -0
- package/src/index.ts +3 -0
- package/src/utils/__tests__/ssoReturn.test.ts +132 -1
- package/src/utils/ssoBounce.ts +33 -0
- package/src/utils/ssoReturn.ts +5 -1
|
@@ -88,6 +88,45 @@ class OxyServicesBase {
|
|
|
88
88
|
getClient() {
|
|
89
89
|
return this.httpService;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Create an app/backend HTTP client linked to this Oxy session.
|
|
93
|
+
*
|
|
94
|
+
* Use this when an app has its own API origin (for example
|
|
95
|
+
* `https://api.syra.fm`) but authentication is owned by the canonical
|
|
96
|
+
* OxyServices instance mounted in OxyProvider. The returned client has its own
|
|
97
|
+
* base URL, cache and request queue, but its bearer token is kept in lockstep
|
|
98
|
+
* with this session and its 401 refresh path delegates back to this session.
|
|
99
|
+
*/
|
|
100
|
+
createLinkedClient(config) {
|
|
101
|
+
const client = new HttpService_1.HttpService(config);
|
|
102
|
+
const syncToken = (accessToken) => {
|
|
103
|
+
const currentAccessToken = client.getAccessToken();
|
|
104
|
+
if (accessToken) {
|
|
105
|
+
if (currentAccessToken !== accessToken) {
|
|
106
|
+
client.setTokens(accessToken);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (currentAccessToken) {
|
|
111
|
+
client.clearTokens();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
syncToken(this.getAccessToken());
|
|
115
|
+
const unsubscribe = this.onTokensChanged(syncToken);
|
|
116
|
+
client.setAuthRefreshHandler(async (reason) => {
|
|
117
|
+
const refreshed = await this.httpService.refreshAccessToken(reason);
|
|
118
|
+
syncToken(refreshed);
|
|
119
|
+
return refreshed;
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
client,
|
|
123
|
+
dispose: () => {
|
|
124
|
+
unsubscribe();
|
|
125
|
+
client.setAuthRefreshHandler(null);
|
|
126
|
+
client.clearTokens();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
91
130
|
/**
|
|
92
131
|
* Get performance metrics
|
|
93
132
|
*/
|
package/dist/cjs/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = exports.darkenColor = 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.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.createCrossDomainAuth = exports.CrossDomainAuth = exports.createAuthManager = exports.AuthManager = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
22
|
exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = 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 = void 0;
|
|
23
|
-
exports.packageInfo = exports.runColdBoot = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.ssoNavigate = exports.ssoAttemptedKey = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = exports.MULTIPART_TLDS = exports.registrableApex = exports.autoDetectAuthWebUrl = exports.getAccountColor = exports.mergeAccountsFromRefreshAll = 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 = void 0;
|
|
23
|
+
exports.packageInfo = exports.runColdBoot = exports.guardActive = exports.isCentralIdPOrigin = exports.buildSsoBounceUrl = exports.getSsoCallbackBootstrapScript = exports.ssoNavigate = exports.ssoCallbackBootstrapKey = exports.ssoAttemptedKey = exports.ssoNoSessionKey = exports.ssoDestKey = exports.ssoGuardKey = exports.ssoStateKey = exports.SSO_GUARD_TTL_MS = exports.SSO_CALLBACK_PATH = exports.generateSsoState = exports.consumeSsoReturn = exports.parseSsoReturnFragment = exports.resolveCentralAuthUrl = exports.CENTRAL_IDP_APEX = exports.CENTRAL_AUTH_URL = exports.MULTIPART_TLDS = exports.registrableApex = exports.autoDetectAuthWebUrl = exports.getAccountColor = exports.mergeAccountsFromRefreshAll = 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 = void 0;
|
|
24
24
|
// Ensure crypto polyfills are loaded before anything else
|
|
25
25
|
require("./crypto/polyfill");
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
@@ -251,7 +251,9 @@ Object.defineProperty(exports, "ssoGuardKey", { enumerable: true, get: function
|
|
|
251
251
|
Object.defineProperty(exports, "ssoDestKey", { enumerable: true, get: function () { return ssoBounce_1.ssoDestKey; } });
|
|
252
252
|
Object.defineProperty(exports, "ssoNoSessionKey", { enumerable: true, get: function () { return ssoBounce_1.ssoNoSessionKey; } });
|
|
253
253
|
Object.defineProperty(exports, "ssoAttemptedKey", { enumerable: true, get: function () { return ssoBounce_1.ssoAttemptedKey; } });
|
|
254
|
+
Object.defineProperty(exports, "ssoCallbackBootstrapKey", { enumerable: true, get: function () { return ssoBounce_1.ssoCallbackBootstrapKey; } });
|
|
254
255
|
Object.defineProperty(exports, "ssoNavigate", { enumerable: true, get: function () { return ssoBounce_1.ssoNavigate; } });
|
|
256
|
+
Object.defineProperty(exports, "getSsoCallbackBootstrapScript", { enumerable: true, get: function () { return ssoBounce_1.getSsoCallbackBootstrapScript; } });
|
|
255
257
|
Object.defineProperty(exports, "buildSsoBounceUrl", { enumerable: true, get: function () { return ssoBounce_1.buildSsoBounceUrl; } });
|
|
256
258
|
Object.defineProperty(exports, "isCentralIdPOrigin", { enumerable: true, get: function () { return ssoBounce_1.isCentralIdPOrigin; } });
|
|
257
259
|
Object.defineProperty(exports, "guardActive", { enumerable: true, get: function () { return ssoBounce_1.guardActive; } });
|
|
@@ -49,6 +49,8 @@ exports.ssoGuardKey = ssoGuardKey;
|
|
|
49
49
|
exports.ssoDestKey = ssoDestKey;
|
|
50
50
|
exports.ssoNoSessionKey = ssoNoSessionKey;
|
|
51
51
|
exports.ssoAttemptedKey = ssoAttemptedKey;
|
|
52
|
+
exports.ssoCallbackBootstrapKey = ssoCallbackBootstrapKey;
|
|
53
|
+
exports.getSsoCallbackBootstrapScript = getSsoCallbackBootstrapScript;
|
|
52
54
|
exports.ssoNavigate = ssoNavigate;
|
|
53
55
|
exports.buildSsoBounceUrl = buildSsoBounceUrl;
|
|
54
56
|
exports.isCentralIdPOrigin = isCentralIdPOrigin;
|
|
@@ -75,6 +77,7 @@ const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
|
|
|
75
77
|
const DEST_KEY_PREFIX = 'oxy_sso_dest:';
|
|
76
78
|
const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
|
|
77
79
|
const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
|
|
80
|
+
const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
|
|
78
81
|
/** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
|
|
79
82
|
function ssoStateKey(origin) {
|
|
80
83
|
return `${STATE_KEY_PREFIX}${origin}`;
|
|
@@ -107,6 +110,35 @@ function ssoNoSessionKey(origin) {
|
|
|
107
110
|
function ssoAttemptedKey(origin) {
|
|
108
111
|
return `${ATTEMPTED_KEY_PREFIX}${origin}`;
|
|
109
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Per-origin marker written by the pre-hydration callback bootstrap.
|
|
115
|
+
*
|
|
116
|
+
* Static Expo exports render unknown paths as `+not-found`; on
|
|
117
|
+
* `/__oxy/sso-callback` that can fail hydration before the React provider has a
|
|
118
|
+
* chance to run `consumeSsoReturn`. The bootstrap runs in the HTML head, moves
|
|
119
|
+
* the URL to a hydratable route while preserving the SSO fragment, and writes
|
|
120
|
+
* this marker so `consumeSsoReturn` still restores the original destination as
|
|
121
|
+
* if the page were physically on the callback path.
|
|
122
|
+
*/
|
|
123
|
+
function ssoCallbackBootstrapKey(origin) {
|
|
124
|
+
return `${CALLBACK_BOOTSTRAP_KEY_PREFIX}${origin}`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Inline script for Expo/static web apps.
|
|
128
|
+
*
|
|
129
|
+
* Must run before the app bundle hydrates. It is intentionally tiny and
|
|
130
|
+
* dependency-free: if the browser lands on the internal callback route with an
|
|
131
|
+
* Oxy SSO fragment, it marks the handoff and rewrites the path to `/` while
|
|
132
|
+
* preserving `#oxy_sso=...`. The normal SDK cold-boot `sso-return` step then
|
|
133
|
+
* consumes the fragment from a route that can hydrate. If the internal route is
|
|
134
|
+
* reached without a valid SSO fragment, it leaves the route via a hard root
|
|
135
|
+
* navigation because there is no session material to preserve.
|
|
136
|
+
*/
|
|
137
|
+
function getSsoCallbackBootstrapScript() {
|
|
138
|
+
const callbackPath = JSON.stringify(exports.SSO_CALLBACK_PATH);
|
|
139
|
+
const bootstrapPrefix = JSON.stringify(CALLBACK_BOOTSTRAP_KEY_PREFIX);
|
|
140
|
+
return `(function(){var p=${callbackPath};if(window.location.pathname!==p)return;var h=window.location.hash||"";if(!/(?:^#|&)oxy_sso=(?:ok|none|error)(?:&|$)/.test(h)){window.location.replace("/");return;}try{window.sessionStorage.setItem(${bootstrapPrefix}+window.location.origin,"1");}catch(e){window.__oxySsoCallbackBootstrapError=e instanceof Error?e.message:String(e);}try{window.history.replaceState(null,"","/"+h);}catch(e){window.__oxySsoCallbackBootstrapError=e instanceof Error?e.message:String(e);window.location.replace("/"+h);}})();`;
|
|
141
|
+
}
|
|
110
142
|
/**
|
|
111
143
|
* Perform the terminal top-level SSO bounce navigation.
|
|
112
144
|
*
|
|
@@ -162,6 +162,8 @@ async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
162
162
|
return null;
|
|
163
163
|
}
|
|
164
164
|
const origin = location.origin;
|
|
165
|
+
const callbackBootstrapKey = (0, ssoBounce_1.ssoCallbackBootstrapKey)(origin);
|
|
166
|
+
const wasCallbackBootstrapped = storage.getItem(callbackBootstrapKey) === '1';
|
|
165
167
|
const expectedState = storage.getItem((0, ssoBounce_1.ssoStateKey)(origin));
|
|
166
168
|
const stateOk = !!ret.state && !!expectedState && ret.state === expectedState;
|
|
167
169
|
// Strip the fragment FIRST so the opaque code never lingers in the address
|
|
@@ -187,7 +189,8 @@ async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
187
189
|
// (so it can be fed to either `history.replaceState` or a `hardRedirect`),
|
|
188
190
|
// or `null` when the page is not on the callback path (nothing to leave).
|
|
189
191
|
const consumeCallbackTarget = () => {
|
|
190
|
-
|
|
192
|
+
storage.removeItem(callbackBootstrapKey);
|
|
193
|
+
if (location.pathname !== ssoBounce_1.SSO_CALLBACK_PATH && !wasCallbackBootstrapped) {
|
|
191
194
|
// Not on the callback path — still drop the dest key (consumed) but there
|
|
192
195
|
// is nothing to navigate away from.
|
|
193
196
|
storage.removeItem((0, ssoBounce_1.ssoDestKey)(origin));
|