@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.
Files changed (48) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/OxyServices.base.js +39 -0
  3. package/dist/cjs/index.js +3 -1
  4. package/dist/cjs/utils/ssoBounce.js +32 -0
  5. package/dist/cjs/utils/ssoReturn.js +4 -1
  6. package/dist/esm/.tsbuildinfo +1 -1
  7. package/dist/esm/OxyServices.base.js +39 -0
  8. package/dist/esm/index.js +1 -1
  9. package/dist/esm/utils/ssoBounce.js +30 -0
  10. package/dist/esm/utils/ssoReturn.js +5 -2
  11. package/dist/types/.tsbuildinfo +1 -1
  12. package/dist/types/HttpService.d.ts +1 -1
  13. package/dist/types/OxyServices.base.d.ts +14 -0
  14. package/dist/types/OxyServices.d.ts +2 -1
  15. package/dist/types/index.d.ts +2 -1
  16. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  17. package/dist/types/mixins/OxyServices.appData.d.ts +1 -0
  18. package/dist/types/mixins/OxyServices.applications.d.ts +1 -0
  19. package/dist/types/mixins/OxyServices.assets.d.ts +1 -0
  20. package/dist/types/mixins/OxyServices.auth.d.ts +1 -0
  21. package/dist/types/mixins/OxyServices.contacts.d.ts +1 -0
  22. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  23. package/dist/types/mixins/OxyServices.features.d.ts +1 -0
  24. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  25. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  26. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  27. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
  28. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  29. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  30. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  31. package/dist/types/mixins/OxyServices.reputation.d.ts +1 -0
  32. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  33. package/dist/types/mixins/OxyServices.silent.d.ts +1 -0
  34. package/dist/types/mixins/OxyServices.sso.d.ts +1 -0
  35. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  36. package/dist/types/mixins/OxyServices.user.d.ts +1 -0
  37. package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
  38. package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -0
  39. package/dist/types/utils/ssoBounce.d.ts +23 -0
  40. package/package.json +1 -1
  41. package/src/HttpService.ts +1 -1
  42. package/src/OxyServices.base.ts +50 -1
  43. package/src/OxyServices.ts +3 -1
  44. package/src/__tests__/linkedClient.test.ts +64 -0
  45. package/src/index.ts +3 -0
  46. package/src/utils/__tests__/ssoReturn.test.ts +132 -1
  47. package/src/utils/ssoBounce.ts +33 -0
  48. package/src/utils/ssoReturn.ts +5 -1
@@ -67,6 +67,7 @@ const GUARD_KEY_PREFIX = 'oxy_sso_guard:';
67
67
  const DEST_KEY_PREFIX = 'oxy_sso_dest:';
68
68
  const NO_SESSION_KEY_PREFIX = 'oxy_sso_no_session:';
69
69
  const ATTEMPTED_KEY_PREFIX = 'oxy_sso_attempted:';
70
+ const CALLBACK_BOOTSTRAP_KEY_PREFIX = 'oxy_sso_callback_bootstrap:';
70
71
 
71
72
  /** Per-origin CSRF state key (matched on return to defeat fragment forgery). */
72
73
  export function ssoStateKey(origin: string): string {
@@ -105,6 +106,38 @@ export function ssoAttemptedKey(origin: string): string {
105
106
  return `${ATTEMPTED_KEY_PREFIX}${origin}`;
106
107
  }
107
108
 
109
+ /**
110
+ * Per-origin marker written by the pre-hydration callback bootstrap.
111
+ *
112
+ * Static Expo exports render unknown paths as `+not-found`; on
113
+ * `/__oxy/sso-callback` that can fail hydration before the React provider has a
114
+ * chance to run `consumeSsoReturn`. The bootstrap runs in the HTML head, moves
115
+ * the URL to a hydratable route while preserving the SSO fragment, and writes
116
+ * this marker so `consumeSsoReturn` still restores the original destination as
117
+ * if the page were physically on the callback path.
118
+ */
119
+ export function ssoCallbackBootstrapKey(origin: string): string {
120
+ return `${CALLBACK_BOOTSTRAP_KEY_PREFIX}${origin}`;
121
+ }
122
+
123
+ /**
124
+ * Inline script for Expo/static web apps.
125
+ *
126
+ * Must run before the app bundle hydrates. It is intentionally tiny and
127
+ * dependency-free: if the browser lands on the internal callback route with an
128
+ * Oxy SSO fragment, it marks the handoff and rewrites the path to `/` while
129
+ * preserving `#oxy_sso=...`. The normal SDK cold-boot `sso-return` step then
130
+ * consumes the fragment from a route that can hydrate. If the internal route is
131
+ * reached without a valid SSO fragment, it leaves the route via a hard root
132
+ * navigation because there is no session material to preserve.
133
+ */
134
+ export function getSsoCallbackBootstrapScript(): string {
135
+ const callbackPath = JSON.stringify(SSO_CALLBACK_PATH);
136
+ const bootstrapPrefix = JSON.stringify(CALLBACK_BOOTSTRAP_KEY_PREFIX);
137
+
138
+ 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);}})();`;
139
+ }
140
+
108
141
  /**
109
142
  * Perform the terminal top-level SSO bounce navigation.
110
143
  *
@@ -29,6 +29,7 @@ import {
29
29
  ssoDestKey,
30
30
  ssoNoSessionKey,
31
31
  ssoAttemptedKey,
32
+ ssoCallbackBootstrapKey,
32
33
  } from './ssoBounce';
33
34
 
34
35
  /**
@@ -257,6 +258,8 @@ export async function consumeSsoReturn(
257
258
  }
258
259
 
259
260
  const origin = location.origin;
261
+ const callbackBootstrapKey = ssoCallbackBootstrapKey(origin);
262
+ const wasCallbackBootstrapped = storage.getItem(callbackBootstrapKey) === '1';
260
263
  const expectedState = storage.getItem(ssoStateKey(origin));
261
264
  const stateOk = !!ret.state && !!expectedState && ret.state === expectedState;
262
265
 
@@ -286,7 +289,8 @@ export async function consumeSsoReturn(
286
289
  // (so it can be fed to either `history.replaceState` or a `hardRedirect`),
287
290
  // or `null` when the page is not on the callback path (nothing to leave).
288
291
  const consumeCallbackTarget = (): string | null => {
289
- if (location.pathname !== SSO_CALLBACK_PATH) {
292
+ storage.removeItem(callbackBootstrapKey);
293
+ if (location.pathname !== SSO_CALLBACK_PATH && !wasCallbackBootstrapped) {
290
294
  // Not on the callback path — still drop the dest key (consumed) but there
291
295
  // is nothing to navigate away from.
292
296
  storage.removeItem(ssoDestKey(origin));