@oxyhq/services 19.1.2 → 19.2.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 (78) hide show
  1. package/android/build.gradle +21 -0
  2. package/android/src/main/AndroidManifest.xml +10 -0
  3. package/android/src/main/java/so/oxy/identity/OxyIdentityModule.kt +84 -0
  4. package/android/src/main/java/so/oxy/identity/OxyIdentityProvider.kt +77 -0
  5. package/android/src/main/java/so/oxy/identity/OxyIdentityStore.kt +76 -0
  6. package/expo-module.config.json +9 -0
  7. package/ios/OxyIdentity.podspec +28 -0
  8. package/ios/OxyIdentityModule.swift +33 -0
  9. package/lib/commonjs/index.js +7 -0
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/ui/context/OxyContext.js +53 -50
  12. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  13. package/lib/commonjs/ui/context/commitSessionFlow.js +91 -0
  14. package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -0
  15. package/lib/commonjs/ui/hooks/useOxyEvent.js +28 -0
  16. package/lib/commonjs/ui/hooks/useOxyEvent.js.map +1 -0
  17. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -9
  18. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  19. package/lib/commonjs/ui/session/createSessionClient.js +12 -8
  20. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  21. package/lib/commonjs/ui/session/tokenTransport.js +22 -32
  22. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
  23. package/lib/module/index.js +1 -0
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/ui/context/OxyContext.js +54 -51
  26. package/lib/module/ui/context/OxyContext.js.map +1 -1
  27. package/lib/module/ui/context/commitSessionFlow.js +87 -0
  28. package/lib/module/ui/context/commitSessionFlow.js.map +1 -0
  29. package/lib/module/ui/hooks/useOxyEvent.js +25 -0
  30. package/lib/module/ui/hooks/useOxyEvent.js.map +1 -0
  31. package/lib/module/ui/screens/FileManagementScreen.js +3 -9
  32. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  33. package/lib/module/ui/session/createSessionClient.js +12 -8
  34. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  35. package/lib/module/ui/session/tokenTransport.js +23 -33
  36. package/lib/module/ui/session/tokenTransport.js.map +1 -1
  37. package/lib/typescript/commonjs/index.d.ts +1 -0
  38. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +39 -0
  41. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -0
  42. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +3 -1
  43. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/ui/hooks/useOxyEvent.d.ts +8 -0
  45. package/lib/typescript/commonjs/ui/hooks/useOxyEvent.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +12 -8
  48. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +11 -9
  50. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
  51. package/lib/typescript/module/index.d.ts +1 -0
  52. package/lib/typescript/module/index.d.ts.map +1 -1
  53. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  54. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +39 -0
  55. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -0
  56. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +3 -1
  57. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  58. package/lib/typescript/module/ui/hooks/useOxyEvent.d.ts +8 -0
  59. package/lib/typescript/module/ui/hooks/useOxyEvent.d.ts.map +1 -0
  60. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  61. package/lib/typescript/module/ui/session/createSessionClient.d.ts +12 -8
  62. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  63. package/lib/typescript/module/ui/session/tokenTransport.d.ts +11 -9
  64. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
  65. package/package.json +29 -17
  66. package/plugins/withSharedIdentityProvider.js +83 -0
  67. package/plugins/withSharedIdentityReader.js +46 -0
  68. package/src/index.ts +1 -0
  69. package/src/ui/context/OxyContext.tsx +51 -49
  70. package/src/ui/context/__tests__/commitSessionFlow.test.ts +192 -0
  71. package/src/ui/context/commitSessionFlow.ts +115 -0
  72. package/src/ui/context/oxyContextTypes.ts +3 -1
  73. package/src/ui/hooks/useOxyEvent.ts +22 -0
  74. package/src/ui/screens/FileManagementScreen.tsx +3 -9
  75. package/src/ui/session/__tests__/createSessionClient.test.ts +5 -5
  76. package/src/ui/session/__tests__/tokenTransport.test.ts +43 -60
  77. package/src/ui/session/createSessionClient.ts +13 -10
  78. package/src/ui/session/tokenTransport.ts +22 -41
@@ -0,0 +1,21 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'so.oxy.identity'
7
+ version = '0.1.0'
8
+
9
+ android {
10
+ namespace "so.oxy.identity"
11
+ defaultConfig {
12
+ versionCode 1
13
+ versionName "0.1.0"
14
+ }
15
+ }
16
+
17
+ dependencies {
18
+ // Hardware-backed EncryptedSharedPreferences (MasterKey AES256_GCM) for the
19
+ // cross-app shared identity keypair.
20
+ implementation "androidx.security:security-crypto:1.1.0-alpha06"
21
+ }
@@ -0,0 +1,10 @@
1
+ <manifest>
2
+ <!--
3
+ Intentionally empty. The signature-protected <permission> and the
4
+ <provider> that hosts the shared identity are declared per-app by the
5
+ Commons config plugin (`withSharedIdentityProvider.js`), NOT here — only
6
+ Commons (the identity vault) should host the provider, and its authority is
7
+ the app's own ${applicationId}.identity. Reader apps add only the matching
8
+ <uses-permission> via `withSharedIdentityReader.js`.
9
+ -->
10
+ </manifest>
@@ -0,0 +1,84 @@
1
+ package so.oxy.identity
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import android.os.Bundle
6
+ import expo.modules.kotlin.exception.Exceptions
7
+ import expo.modules.kotlin.modules.Module
8
+ import expo.modules.kotlin.modules.ModuleDefinition
9
+
10
+ /**
11
+ * JS bridge for the cross-app shared Oxy identity.
12
+ *
13
+ * - `putShared` persists the keypair into THIS app's own hardware-backed
14
+ * EncryptedSharedPreferences (only Commons calls this).
15
+ * - `getShared` resolves the keypair from the LOCAL store first (Commons reading
16
+ * itself), then from the Commons ContentProvider — prod authority, then the
17
+ * dev-variant authority. The `signature` permission on the provider means only
18
+ * apps signed with the shared Oxy release key can resolve it.
19
+ * - `hasShared` / `clearShared` are the check + local teardown helpers.
20
+ *
21
+ * Every failure resolves to null / no-op so the JS layer degrades to the app's
22
+ * normal interactive sign-in path.
23
+ */
24
+ class OxyIdentityModule : Module() {
25
+ private val context: Context
26
+ get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
27
+
28
+ override fun definition() = ModuleDefinition {
29
+ Name("OxyIdentity")
30
+
31
+ AsyncFunction("getShared") {
32
+ readShared()
33
+ }
34
+
35
+ AsyncFunction("putShared") { privateKey: String, publicKey: String ->
36
+ OxyIdentityStore.write(context, privateKey, publicKey)
37
+ }
38
+
39
+ AsyncFunction("hasShared") {
40
+ readShared() != null
41
+ }
42
+
43
+ AsyncFunction("clearShared") {
44
+ OxyIdentityStore.clear(context)
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Local EncryptedSharedPreferences first, then the Commons provider (prod
50
+ * authority, then dev). Returns null on any failure.
51
+ */
52
+ private fun readShared(): Map<String, String>? {
53
+ runCatching { OxyIdentityStore.read(context) }.getOrNull()?.let { (priv, pub) ->
54
+ return mapOf("privateKey" to priv, "publicKey" to pub)
55
+ }
56
+
57
+ for (authority in PROVIDER_AUTHORITIES) {
58
+ val result = runCatching { callProvider(authority) }.getOrNull()
59
+ if (result != null) return result
60
+ }
61
+ return null
62
+ }
63
+
64
+ private fun callProvider(authority: String): Map<String, String>? {
65
+ val uri = Uri.parse("content://$authority")
66
+ val bundle: Bundle = context.contentResolver.call(uri, METHOD_GET_SHARED, null, null)
67
+ ?: return null
68
+ val priv = bundle.getString(OxyIdentityStore.KEY_PRIVATE) ?: return null
69
+ val pub = bundle.getString(OxyIdentityStore.KEY_PUBLIC) ?: return null
70
+ if (priv.isEmpty() || pub.isEmpty()) return null
71
+ return mapOf("privateKey" to priv, "publicKey" to pub)
72
+ }
73
+
74
+ companion object {
75
+ private const val METHOD_GET_SHARED = "getShared"
76
+
77
+ // Commons hosts the provider at "${applicationId}.identity". Try the prod
78
+ // app id first, then the dev variant ("so.oxy.commons.dev").
79
+ private val PROVIDER_AUTHORITIES = listOf(
80
+ "so.oxy.commons.identity",
81
+ "so.oxy.commons.dev.identity"
82
+ )
83
+ }
84
+ }
@@ -0,0 +1,77 @@
1
+ package so.oxy.identity
2
+
3
+ import android.content.ContentProvider
4
+ import android.content.ContentValues
5
+ import android.content.Context
6
+ import android.content.pm.PackageManager
7
+ import android.database.Cursor
8
+ import android.net.Uri
9
+ import android.os.Bundle
10
+
11
+ /**
12
+ * Cross-process read surface for the shared Oxy identity, hosted ONLY by Commons
13
+ * (declared per-app by the `withSharedIdentityProvider` config plugin with a
14
+ * `signature`-level permission, authority `${applicationId}.identity`).
15
+ *
16
+ * `call("getShared")` returns the keypair from Commons's own
17
+ * EncryptedSharedPreferences — but ONLY after verifying the caller is signed
18
+ * with the SAME certificate. That signature check is belt-and-suspenders on top
19
+ * of the manifest's `signature` permission: even if the permission gate were
20
+ * ever misconfigured, a differently-signed caller still gets null.
21
+ *
22
+ * All standard CRUD operations are no-ops; this provider exists solely for the
23
+ * `call()` channel.
24
+ */
25
+ class OxyIdentityProvider : ContentProvider() {
26
+ override fun onCreate(): Boolean = true
27
+
28
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
29
+ if (method != METHOD_GET_SHARED) return null
30
+ val ctx = context ?: return null
31
+ if (!callerSignatureMatches(ctx)) return null
32
+
33
+ val pair = runCatching { OxyIdentityStore.read(ctx) }.getOrNull() ?: return null
34
+ return Bundle().apply {
35
+ putString(OxyIdentityStore.KEY_PRIVATE, pair.first)
36
+ putString(OxyIdentityStore.KEY_PUBLIC, pair.second)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * True when the calling package shares this app's signing certificate. Uses
42
+ * `checkSignatures` (deprecated but still the simplest correct cross-package
43
+ * signature comparison; returns SIGNATURE_MATCH only for same-cert apps).
44
+ */
45
+ private fun callerSignatureMatches(ctx: Context): Boolean {
46
+ val caller = callingPackage ?: return false
47
+ return runCatching {
48
+ @Suppress("DEPRECATION")
49
+ ctx.packageManager.checkSignatures(caller, ctx.packageName) == PackageManager.SIGNATURE_MATCH
50
+ }.getOrDefault(false)
51
+ }
52
+
53
+ override fun query(
54
+ uri: Uri,
55
+ projection: Array<out String>?,
56
+ selection: String?,
57
+ selectionArgs: Array<out String>?,
58
+ sortOrder: String?
59
+ ): Cursor? = null
60
+
61
+ override fun getType(uri: Uri): String? = null
62
+
63
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
64
+
65
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
66
+
67
+ override fun update(
68
+ uri: Uri,
69
+ values: ContentValues?,
70
+ selection: String?,
71
+ selectionArgs: Array<out String>?
72
+ ): Int = 0
73
+
74
+ companion object {
75
+ private const val METHOD_GET_SHARED = "getShared"
76
+ }
77
+ }
@@ -0,0 +1,76 @@
1
+ package so.oxy.identity
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import androidx.security.crypto.EncryptedSharedPreferences
6
+ import androidx.security.crypto.MasterKey
7
+
8
+ /**
9
+ * Shared accessor for the hardware-backed EncryptedSharedPreferences that holds
10
+ * the cross-app Oxy identity keypair.
11
+ *
12
+ * Used by BOTH [OxyIdentityModule] (the JS bridge / local read + write) and
13
+ * [OxyIdentityProvider] (the cross-process read surface) so the store name and
14
+ * the AES256 encryption scheme can never drift between the two halves.
15
+ *
16
+ * ## Single memoized instance (CRITICAL)
17
+ *
18
+ * `EncryptedSharedPreferences.create()` must be called AT MOST ONCE per file per
19
+ * process. It is NOT safe to re-instantiate: when a second instance is created
20
+ * for the same file while another is live (e.g. the JS write thread and a Binder
21
+ * thread serving [OxyIdentityProvider.call] concurrently), Tink's keyset load
22
+ * races and the next decrypt throws `AEADBadTagException` — which silently turned
23
+ * every cross-app read into "no shared identity". So the instance is created once,
24
+ * lazily, under a lock, keyed on the process-global application context, and
25
+ * reused for all reads/writes/provider calls.
26
+ */
27
+ internal object OxyIdentityStore {
28
+ const val PREFS_NAME = "oxy_shared_identity"
29
+ const val KEY_PRIVATE = "priv"
30
+ const val KEY_PUBLIC = "pub"
31
+
32
+ @Volatile private var cachedPrefs: SharedPreferences? = null
33
+
34
+ private fun prefs(context: Context): SharedPreferences {
35
+ cachedPrefs?.let { return it }
36
+ return synchronized(this) {
37
+ cachedPrefs ?: run {
38
+ val appContext = context.applicationContext
39
+ val masterKey = MasterKey.Builder(appContext)
40
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
41
+ .build()
42
+ val created = EncryptedSharedPreferences.create(
43
+ appContext,
44
+ PREFS_NAME,
45
+ masterKey,
46
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
47
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
48
+ )
49
+ cachedPrefs = created
50
+ created
51
+ }
52
+ }
53
+ }
54
+
55
+ /** Read the stored keypair as (privateKey, publicKey), or null when absent/blank. */
56
+ fun read(context: Context): Pair<String, String>? {
57
+ val p = prefs(context)
58
+ val priv = p.getString(KEY_PRIVATE, null) ?: return null
59
+ val pub = p.getString(KEY_PUBLIC, null) ?: return null
60
+ if (priv.isEmpty() || pub.isEmpty()) return null
61
+ return priv to pub
62
+ }
63
+
64
+ fun write(context: Context, priv: String, pub: String) {
65
+ // commit() (synchronous) so a cross-process reader that fires immediately
66
+ // after the write is guaranteed to see the flushed value.
67
+ prefs(context).edit()
68
+ .putString(KEY_PRIVATE, priv)
69
+ .putString(KEY_PUBLIC, pub)
70
+ .commit()
71
+ }
72
+
73
+ fun clear(context: Context) {
74
+ prefs(context).edit().clear().commit()
75
+ }
76
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["OxyIdentityModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["so.oxy.identity.OxyIdentityModule"]
8
+ }
9
+ }
@@ -0,0 +1,28 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'OxyIdentity'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '16.4',
15
+ :tvos => '16.4'
16
+ }
17
+ s.swift_version = '5.9'
18
+ s.source = { git: 'https://github.com/oxyhq/sdk.git' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+
23
+ s.source_files = "**/*.{h,m,mm,swift}"
24
+ s.pod_target_xcconfig = {
25
+ 'DEFINES_MODULE' => 'YES',
26
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
27
+ }
28
+ end
@@ -0,0 +1,33 @@
1
+ import ExpoModulesCore
2
+
3
+ /**
4
+ * iOS no-op implementation of the shared Oxy identity bridge.
5
+ *
6
+ * On Apple platforms the cross-app identity share is handled directly by
7
+ * `@oxyhq/core`'s `KeyManager` via the Keychain Access Group
8
+ * (`group.so.oxy.shared`) — there is no ContentProvider equivalent to wrap. So
9
+ * every function here resolves to `nil` / no-op, which makes the JS
10
+ * `loadSharedIdentityBridge()` seam a pass-through on iOS: `KeyManager`'s iOS
11
+ * branches keep using `expo-secure-store` with the keychain group untouched.
12
+ */
13
+ public class OxyIdentityModule: Module {
14
+ public func definition() -> ModuleDefinition {
15
+ Name("OxyIdentity")
16
+
17
+ AsyncFunction("getShared") { () -> [String: String]? in
18
+ return nil
19
+ }
20
+
21
+ AsyncFunction("putShared") { (_ privateKey: String, _ publicKey: String) in
22
+ // No-op on iOS: the keychain-access-group path in KeyManager owns writes.
23
+ }
24
+
25
+ AsyncFunction("hasShared") { () -> Bool in
26
+ return false
27
+ }
28
+
29
+ AsyncFunction("clearShared") { () in
30
+ // No-op on iOS.
31
+ }
32
+ }
33
+ }
@@ -441,6 +441,12 @@ Object.defineProperty(exports, "useOxy", {
441
441
  return _OxyContext.useOxy;
442
442
  }
443
443
  });
444
+ Object.defineProperty(exports, "useOxyEvent", {
445
+ enumerable: true,
446
+ get: function () {
447
+ return _useOxyEvent.useOxyEvent;
448
+ }
449
+ });
444
450
  Object.defineProperty(exports, "usePrivacySettings", {
445
451
  enumerable: true,
446
452
  get: function () {
@@ -645,6 +651,7 @@ var _mutationKeys = require("./ui/hooks/mutations/mutationKeys.js");
645
651
  var _queryKeys = require("./ui/hooks/queries/queryKeys.js");
646
652
  var _useMutationStatus = require("./ui/hooks/useMutationStatus.js");
647
653
  var _useOnlineStatus = require("./ui/hooks/useOnlineStatus.js");
654
+ var _useOxyEvent = require("./ui/hooks/useOxyEvent.js");
648
655
  var _errorHandlers = require("./ui/utils/errorHandlers.js");
649
656
  var _useFileFiltering = require("./ui/hooks/useFileFiltering.js");
650
657
  var _OxySignInButton = _interopRequireDefault(require("./ui/components/OxySignInButton.js"));
@@ -1 +1 @@
1
- {"version":3,"names":["_core","require","_reactNative","_OxyProvider","_interopRequireDefault","_OxyContext","_useAuth","_FontLoader","_authStore","_accountStore","_assetStore","_useAssets","_useFileDownloadUrl","_useFollow","_useStorage","_useAccountQueries","_useServicesQueries","_useSecurityQueries","_usePaymentQueries","_useAccountMutations","_useServicesMutations","_mutationFactory","_mutationKeys","_queryKeys","_useMutationStatus","_useOnlineStatus","_errorHandlers","_useFileFiltering","_OxySignInButton","_OxyAuthPrompt","_OxyOAuthCallback","_OxyConsentScreen","_RequireOxyAuth","_FollowButton","_OxyPayButton","_LogoIcon","_LogoText","_ProfileButton","_useSwitchableAccounts","_ProfileScreen","_ManageAccountScreen","_NotificationsScreen","_PreferencesScreen","_ConnectedAppsScreen","_CreateAccountScreen","_AccountMembersScreen","_AccountSettingsScreen","_bottomSheetManager","_accountDialogManager","e","__esModule","default","setPlatformOS","Platform","OS"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_core","require","_reactNative","_OxyProvider","_interopRequireDefault","_OxyContext","_useAuth","_FontLoader","_authStore","_accountStore","_assetStore","_useAssets","_useFileDownloadUrl","_useFollow","_useStorage","_useAccountQueries","_useServicesQueries","_useSecurityQueries","_usePaymentQueries","_useAccountMutations","_useServicesMutations","_mutationFactory","_mutationKeys","_queryKeys","_useMutationStatus","_useOnlineStatus","_useOxyEvent","_errorHandlers","_useFileFiltering","_OxySignInButton","_OxyAuthPrompt","_OxyOAuthCallback","_OxyConsentScreen","_RequireOxyAuth","_FollowButton","_OxyPayButton","_LogoIcon","_LogoText","_ProfileButton","_useSwitchableAccounts","_ProfileScreen","_ManageAccountScreen","_NotificationsScreen","_PreferencesScreen","_ConnectedAppsScreen","_CreateAccountScreen","_AccountMembersScreen","_AccountSettingsScreen","_bottomSheetManager","_accountDialogManager","e","__esModule","default","setPlatformOS","Platform","OS"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
@@ -26,6 +26,7 @@ var _useAvatarPicker = require("../hooks/useAvatarPicker.js");
26
26
  var _accountStore = require("../stores/accountStore.js");
27
27
  var _index = require("../session/index.js");
28
28
  var _oxyContextHelpers = require("./oxyContextHelpers.js");
29
+ var _commitSessionFlow = require("./commitSessionFlow.js");
29
30
  var _useOxyAccountGraph = require("./useOxyAccountGraph.js");
30
31
  var _jsxRuntime = require("react/jsx-runtime");
31
32
  const OxyContext = /*#__PURE__*/(0, _react.createContext)(null);
@@ -240,8 +241,16 @@ const OxyProvider = ({
240
241
  // the one-shot join redirect.
241
242
  const sessionClientPairRef = (0, _react.useRef)(null);
242
243
  if (!sessionClientPairRef.current) {
243
- sessionClientPairRef.current = (0, _index.createSessionClient)(oxyServices, authStore, () => {
244
- (0, _useAuthOperations.clearPersistedAuthSafe)(authStore, logger);
244
+ sessionClientPairRef.current = (0, _index.createSessionClient)(oxyServices, origin => {
245
+ // Erase the DURABLE device credential only on a `request`-origin verdict
246
+ // (a REST sign-out / revocation). A `push`-origin empty state is a socket
247
+ // broadcast that may be a transient reconnect artifact — clearing the
248
+ // credential on it would strand the user signed out with no way back, so
249
+ // we clear only the local UI session and keep the credential (a dead one
250
+ // re-mints to `no_active_session` and resolves signed-out on next boot).
251
+ if (origin === 'request') {
252
+ (0, _useAuthOperations.clearPersistedAuthSafe)(authStore, logger);
253
+ }
245
254
  void clearSessionStateRef.current().catch(clearError => {
246
255
  logger('Failed to clear local state on remote sign-out', clearError);
247
256
  });
@@ -261,8 +270,12 @@ const OxyProvider = ({
261
270
  return;
262
271
  }
263
272
  if (state.accounts.length === 0) {
273
+ // Clear the LOCAL UI session on any applied empty state, but NEVER the
274
+ // durable device credential here: `syncFromClient` runs for socket-pushed
275
+ // (possibly transient) states too, and wiping the credential on one would
276
+ // strand the user signed out. The credential wipe is gated on a
277
+ // `request`-origin verdict in `onUnauthenticated` above.
264
278
  sessionClientHost.setCurrentAccountId(null);
265
- (0, _useAuthOperations.clearPersistedAuthSafe)(authStore, logger);
266
279
  await clearSessionState();
267
280
  return;
268
281
  }
@@ -289,7 +302,7 @@ const OxyProvider = ({
289
302
  loginSuccess(activeUser);
290
303
  }
291
304
  sessionClientHost.setCurrentAccountId(latest.activeAccountId);
292
- }, [oxyServices, sessionClient, sessionClientHost, authStore, updateSessions, setActiveSessionId, loginSuccess, clearSessionState, logger]);
305
+ }, [oxyServices, sessionClient, sessionClientHost, updateSessions, setActiveSessionId, loginSuccess, clearSessionState, logger]);
293
306
  const syncFromClientRef = (0, _react.useRef)(syncFromClient);
294
307
  syncFromClientRef.current = syncFromClient;
295
308
  const syncDeviceCredentialToHost = (0, _react.useCallback)(async () => {
@@ -476,45 +489,27 @@ const OxyProvider = ({
476
489
  setActiveSessionId(input.sessionId);
477
490
  }
478
491
 
479
- // Register into the device set. A deliberate sign-in ACTIVATES the account
480
- // (`registerAndActivate`); the cold boot only ensures membership and lets
481
- // the server's own `activeAccountId` win (`addCurrentAccount`). Both then
482
- // start the socket + project. Best-effort a failure never fails the
483
- // sign-in (cold boot re-registers on the next load).
484
- try {
485
- if (options.activate) {
486
- await sessionClient.registerAndActivate(input.userId);
487
- } else {
488
- await sessionClient.addCurrentAccount();
489
- }
490
- await sessionClient.start();
491
- await syncFromClient();
492
- } catch (registrationError) {
493
- _core.logger.warn('commitSession: device-set registration failed', {
494
- component: 'OxyContext',
495
- method: 'commitSession'
496
- }, registrationError);
497
- }
498
-
499
- // Hydrate the full user (the commit input carries only minimal data). Fall
500
- // back to the minimal shape if the profile fetch fails.
501
- let fullUser = null;
502
- try {
503
- fullUser = await oxyServices.getCurrentUser();
504
- } catch (profileError) {
505
- if (__DEV__) {
506
- _core.logger.debug('Failed to fetch full user on commit; using minimal fallback', {
507
- component: 'OxyContext',
508
- method: 'commitSession'
509
- }, profileError);
510
- }
511
- fullUser = input.user ?? null;
512
- }
513
- if (fullUser) {
514
- loginSuccess(fullUser);
515
- onAuthStateChangeRef.current?.(fullUser);
516
- }
517
- markAuthResolvedRef.current();
492
+ // Register into the device set, hydrate the full user, and flip the
493
+ // auth-resolution gate. A deliberate sign-in ACTIVATES the account and
494
+ // BLOCKS on the reconcile (register + socket + sessions projection) before
495
+ // resolving unchanged ordering. The cold boot resolves auth from the
496
+ // profile fetch FIRST and reconciles the device set in the background, so
497
+ // first paint is not held behind those extra round-trips. Reconcile is
498
+ // best-effort — a failure never fails the sign-in (cold boot re-registers
499
+ // on the next load).
500
+ await (0, _commitSessionFlow.commitDeviceSetAndResolve)({
501
+ activate: options.activate,
502
+ userId: input.userId,
503
+ fallbackUser: input.user ?? null,
504
+ registerAndActivate: userId => sessionClient.registerAndActivate(userId),
505
+ addCurrentAccount: () => sessionClient.addCurrentAccount(),
506
+ startSocket: () => sessionClient.start(),
507
+ syncFromClient,
508
+ getCurrentUser: () => oxyServices.getCurrentUser(),
509
+ loginSuccess,
510
+ onAuthStateChange: onAuthStateChangeRef.current,
511
+ markAuthResolved: markAuthResolvedRef.current
512
+ });
518
513
  if (options.activate && options.hubSync && hubSync && (0, _isWebBrowser.isWebBrowser)()) {
519
514
  try {
520
515
  await (0, _core.syncHubAfterSignIn)(oxyServices, {
@@ -577,7 +572,13 @@ const OxyProvider = ({
577
572
  return;
578
573
  }
579
574
  void _reactNative.Linking.openURL(url);
580
- }
575
+ },
576
+ // Native-only probe so the controller can detect an installed Commons and
577
+ // deep-link straight into its approve screen (keeping the QR/polling live
578
+ // as fallback). `undefined` on web mirrors the `openUrl` split — the
579
+ // controller short-circuits, so `redirectToAuthorize` never runs for the
580
+ // `oxycommons://` payload.
581
+ canOpenApp: (0, _isWebBrowser.isWebBrowser)() ? undefined : url => _reactNative.Linking.canOpenURL(url)
581
582
  });
582
583
  }
583
584
  const accountDialogController = accountDialogControllerRef.current;
@@ -708,10 +709,10 @@ const OxyProvider = ({
708
709
  if (document.visibilityState !== 'visible') return;
709
710
  const reconcile = async () => {
710
711
  if (!oxyServices.getAccessToken() && sessionClientHost.getDeviceCredential()) {
711
- await (0, _core.refreshPersistedSession)({
712
- oxy: oxyServices,
713
- store: authStore
714
- });
712
+ // Route through the ONE shared single-flight the scheduler/preflight/401
713
+ // use — never a private mint lane — so a tab-focus reconcile can't
714
+ // double-rotate the device secret against them.
715
+ await oxyServices.httpService.refreshAccessToken('preflight');
715
716
  }
716
717
  if (!oxyServices.getAccessToken() && !sessionClientHost.getDeviceCredential()) {
717
718
  return;
@@ -723,7 +724,7 @@ const OxyProvider = ({
723
724
  };
724
725
  document.addEventListener('visibilitychange', onVisibility);
725
726
  return () => document.removeEventListener('visibilitychange', onVisibility);
726
- }, [oxyServices, sessionClient, sessionClientHost, authStore, syncFromClient]);
727
+ }, [oxyServices, sessionClient, sessionClientHost, syncFromClient]);
727
728
 
728
729
  // Exposed `refreshSessions`: re-bootstrap the server-authoritative device
729
730
  // state and reproject — the manual counterpart to the realtime socket.
@@ -819,6 +820,7 @@ const OxyProvider = ({
819
820
  storageKeyPrefix,
820
821
  clientId,
821
822
  oxyServices,
823
+ sessionClient,
822
824
  useFollow: useFollowHook,
823
825
  showBottomSheet: showBottomSheetForContext,
824
826
  openAvatarPicker,
@@ -830,7 +832,7 @@ const OxyProvider = ({
830
832
  switchToAccount,
831
833
  refreshAccounts,
832
834
  createAccount: createAccountFn
833
- }), [user, sessions, activeSessionId, isAuthenticated, isLoading, tokenReady, hasAccessToken, canUsePrivateApi, isPrivateApiPending, authResolved, storage, error, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, hasIdentity, getPublicKey, signIn, signInWithPassword, completeTwoFactorSignIn, revokeSuspiciousSignIn, handleWebSession, logout, logoutAll, switchSessionForContext, refreshSessionsForContext, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, clearSessionState, clearAllAccountData, storageKeyPrefix, clientId, oxyServices, useFollowHook, showBottomSheetForContext, openAvatarPicker, accountDialogController, accountDialogOpen, openAccountDialog, closeAccountDialog, accounts, switchToAccount, refreshAccounts, createAccountFn]);
835
+ }), [user, sessions, activeSessionId, isAuthenticated, isLoading, tokenReady, hasAccessToken, canUsePrivateApi, isPrivateApiPending, authResolved, storage, error, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, hasIdentity, getPublicKey, signIn, signInWithPassword, completeTwoFactorSignIn, revokeSuspiciousSignIn, handleWebSession, logout, logoutAll, switchSessionForContext, refreshSessionsForContext, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, clearSessionState, clearAllAccountData, storageKeyPrefix, clientId, oxyServices, sessionClient, useFollowHook, showBottomSheetForContext, openAvatarPicker, accountDialogController, accountDialogOpen, openAccountDialog, closeAccountDialog, accounts, switchToAccount, refreshAccounts, createAccountFn]);
834
836
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(OxyContext.Provider, {
835
837
  value: contextValue,
836
838
  children: children
@@ -886,6 +888,7 @@ const LOADING_STATE = {
886
888
  storageKeyPrefix: 'oxy_session',
887
889
  clientId: null,
888
890
  oxyServices: LOADING_STATE_OXY_SERVICES,
891
+ sessionClient: null,
889
892
  openAvatarPicker: () => {},
890
893
  accountDialogController: null,
891
894
  isAccountDialogOpen: false,