@oxyhq/core 1.11.11 → 1.11.13

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 (130) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/CrossDomainAuth.js +3 -1
  3. package/dist/cjs/HttpService.js +227 -51
  4. package/dist/cjs/OxyServices.base.js +9 -0
  5. package/dist/cjs/OxyServices.js +8 -3
  6. package/dist/cjs/crypto/index.js +3 -1
  7. package/dist/cjs/crypto/keyManager.js +476 -172
  8. package/dist/cjs/crypto/polyfill.js +14 -65
  9. package/dist/cjs/crypto/recoveryPhrase.js +30 -11
  10. package/dist/cjs/crypto/signatureService.js +25 -60
  11. package/dist/cjs/i18n/locales/en-US.json +46 -1
  12. package/dist/cjs/i18n/locales/es-ES.json +46 -1
  13. package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
  14. package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
  15. package/dist/cjs/index.js +7 -2
  16. package/dist/cjs/mixins/OxyServices.assets.js +9 -4
  17. package/dist/cjs/mixins/OxyServices.auth.js +27 -0
  18. package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
  19. package/dist/cjs/mixins/OxyServices.features.js +0 -11
  20. package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
  21. package/dist/cjs/mixins/OxyServices.language.js +5 -36
  22. package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
  23. package/dist/cjs/mixins/OxyServices.security.js +13 -2
  24. package/dist/cjs/mixins/OxyServices.user.js +70 -38
  25. package/dist/cjs/mixins/OxyServices.utility.js +19 -43
  26. package/dist/cjs/mixins/index.js +11 -3
  27. package/dist/cjs/utils/accountUtils.js +71 -2
  28. package/dist/cjs/utils/asyncUtils.js +34 -5
  29. package/dist/cjs/utils/deviceManager.js +5 -36
  30. package/dist/cjs/utils/platformCrypto.js +165 -0
  31. package/dist/cjs/utils/platformCrypto.native.js +123 -0
  32. package/dist/esm/.tsbuildinfo +1 -1
  33. package/dist/esm/CrossDomainAuth.js +3 -1
  34. package/dist/esm/HttpService.js +228 -52
  35. package/dist/esm/OxyServices.base.js +9 -0
  36. package/dist/esm/OxyServices.js +8 -3
  37. package/dist/esm/crypto/index.js +1 -1
  38. package/dist/esm/crypto/keyManager.js +473 -138
  39. package/dist/esm/crypto/polyfill.js +14 -32
  40. package/dist/esm/crypto/recoveryPhrase.js +30 -11
  41. package/dist/esm/crypto/signatureService.js +25 -27
  42. package/dist/esm/i18n/locales/en-US.json +46 -1
  43. package/dist/esm/i18n/locales/es-ES.json +46 -1
  44. package/dist/esm/i18n/locales/locales/en-US.json +46 -1
  45. package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
  46. package/dist/esm/index.js +2 -2
  47. package/dist/esm/mixins/OxyServices.assets.js +9 -4
  48. package/dist/esm/mixins/OxyServices.auth.js +27 -0
  49. package/dist/esm/mixins/OxyServices.contacts.js +47 -0
  50. package/dist/esm/mixins/OxyServices.features.js +0 -11
  51. package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
  52. package/dist/esm/mixins/OxyServices.language.js +5 -3
  53. package/dist/esm/mixins/OxyServices.redirect.js +6 -2
  54. package/dist/esm/mixins/OxyServices.security.js +13 -2
  55. package/dist/esm/mixins/OxyServices.user.js +70 -38
  56. package/dist/esm/mixins/OxyServices.utility.js +19 -10
  57. package/dist/esm/mixins/index.js +11 -3
  58. package/dist/esm/utils/accountUtils.js +67 -1
  59. package/dist/esm/utils/asyncUtils.js +34 -5
  60. package/dist/esm/utils/deviceManager.js +5 -3
  61. package/dist/esm/utils/platformCrypto.js +125 -0
  62. package/dist/esm/utils/platformCrypto.native.js +80 -0
  63. package/dist/types/.tsbuildinfo +1 -1
  64. package/dist/types/HttpService.d.ts +47 -3
  65. package/dist/types/OxyServices.base.d.ts +7 -0
  66. package/dist/types/OxyServices.d.ts +36 -3
  67. package/dist/types/crypto/index.d.ts +1 -1
  68. package/dist/types/crypto/keyManager.d.ts +110 -9
  69. package/dist/types/crypto/polyfill.d.ts +3 -1
  70. package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
  71. package/dist/types/crypto/signatureService.d.ts +4 -0
  72. package/dist/types/index.d.ts +4 -3
  73. package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
  74. package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
  75. package/dist/types/mixins/OxyServices.auth.d.ts +16 -0
  76. package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
  77. package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
  78. package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
  79. package/dist/types/mixins/OxyServices.features.d.ts +2 -7
  80. package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
  81. package/dist/types/mixins/OxyServices.karma.d.ts +1 -0
  82. package/dist/types/mixins/OxyServices.language.d.ts +1 -0
  83. package/dist/types/mixins/OxyServices.location.d.ts +1 -0
  84. package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -0
  85. package/dist/types/mixins/OxyServices.payment.d.ts +1 -0
  86. package/dist/types/mixins/OxyServices.popup.d.ts +1 -0
  87. package/dist/types/mixins/OxyServices.privacy.d.ts +1 -0
  88. package/dist/types/mixins/OxyServices.redirect.d.ts +1 -0
  89. package/dist/types/mixins/OxyServices.security.d.ts +1 -0
  90. package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
  91. package/dist/types/mixins/OxyServices.user.d.ts +40 -11
  92. package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
  93. package/dist/types/mixins/index.d.ts +52 -4
  94. package/dist/types/models/interfaces.d.ts +62 -3
  95. package/dist/types/utils/accountUtils.d.ts +41 -1
  96. package/dist/types/utils/asyncUtils.d.ts +6 -2
  97. package/dist/types/utils/platformCrypto.d.ts +87 -0
  98. package/dist/types/utils/platformCrypto.native.d.ts +54 -0
  99. package/package.json +28 -1
  100. package/src/CrossDomainAuth.ts +12 -10
  101. package/src/HttpService.ts +264 -51
  102. package/src/OxyServices.base.ts +10 -0
  103. package/src/OxyServices.ts +9 -4
  104. package/src/crypto/__tests__/keyManager.test.ts +336 -0
  105. package/src/crypto/index.ts +6 -1
  106. package/src/crypto/keyManager.ts +529 -151
  107. package/src/crypto/polyfill.ts +14 -34
  108. package/src/crypto/recoveryPhrase.ts +56 -17
  109. package/src/crypto/signatureService.ts +25 -29
  110. package/src/i18n/locales/en-US.json +46 -1
  111. package/src/i18n/locales/es-ES.json +46 -1
  112. package/src/index.ts +16 -3
  113. package/src/mixins/OxyServices.assets.ts +15 -11
  114. package/src/mixins/OxyServices.auth.ts +28 -0
  115. package/src/mixins/OxyServices.contacts.ts +73 -0
  116. package/src/mixins/OxyServices.features.ts +2 -12
  117. package/src/mixins/OxyServices.fedcm.ts +4 -3
  118. package/src/mixins/OxyServices.language.ts +6 -4
  119. package/src/mixins/OxyServices.redirect.ts +6 -2
  120. package/src/mixins/OxyServices.security.ts +18 -8
  121. package/src/mixins/OxyServices.user.ts +90 -49
  122. package/src/mixins/OxyServices.utility.ts +19 -10
  123. package/src/mixins/index.ts +58 -7
  124. package/src/models/interfaces.ts +65 -3
  125. package/src/utils/__tests__/asyncUtils.test.ts +187 -0
  126. package/src/utils/accountUtils.ts +82 -2
  127. package/src/utils/asyncUtils.ts +39 -9
  128. package/src/utils/deviceManager.ts +7 -4
  129. package/src/utils/platformCrypto.native.ts +101 -0
  130. package/src/utils/platformCrypto.ts +145 -0
@@ -54,17 +54,52 @@ export declare class HttpService {
54
54
  private requestMetrics;
55
55
  constructor(config: OxyConfig);
56
56
  /**
57
- * Robust FormData detection that works in browser and Node.js environments
58
- * Checks multiple conditions to handle different FormData implementations
57
+ * Robust FormData detection that works in browser, React Native, and
58
+ * Node.js polyfill environments.
59
+ *
60
+ * Why we don't use `instanceof FormData` alone:
61
+ * - React Native's FormData is a separate class, not the browser one —
62
+ * `instanceof FormData` is true only inside the JS runtime that
63
+ * instantiated the value (browser-side polyfills also have their own).
64
+ * - The Node.js `form-data` polyfill ships its own constructor.
65
+ *
66
+ * Why we explicitly reject `URLSearchParams`:
67
+ * - `URLSearchParams` ALSO exposes `append` / `get` / `has`, so the
68
+ * duck-type fallback below would have misidentified it as FormData.
69
+ * - We want urlencoded payloads to take the JSON-stringify path so the
70
+ * server receives them as `application/x-www-form-urlencoded` instead
71
+ * of an empty multipart body.
59
72
  */
60
73
  private isFormData;
61
74
  /**
62
75
  * Main request method - handles everything in one place
63
76
  */
64
77
  request<T = unknown>(config: RequestConfig): Promise<T>;
78
+ /**
79
+ * Upload via XMLHttpRequest (React Native FormData workaround).
80
+ *
81
+ * Expo SDK 56's "winter fetch" cannot serialize RN file descriptors
82
+ * (`{uri, type, name}`) — `convertFormDataAsync` rejects them as
83
+ * `Unsupported FormDataPart implementation`. RN's native XHR streams
84
+ * the file from disk correctly, so multipart uploads go through XHR
85
+ * on RN only.
86
+ *
87
+ * Returns a standard `Response` so downstream parsing in `request()`
88
+ * (status checks, 401/403 retries, JSON/blob/text parsing) is identical
89
+ * to the fetch path.
90
+ */
91
+ private uploadViaXHR;
92
+ /**
93
+ * Parse raw header string from `XMLHttpRequest.getAllResponseHeaders()`
94
+ * into a `Headers`-compatible object.
95
+ */
96
+ private static parseXHRHeaders;
65
97
  /**
66
98
  * Generate cache key efficiently
67
- * Uses simple hash for large objects to avoid expensive JSON.stringify
99
+ * Uses a content-addressed hash for large payloads so two requests with
100
+ * the same shape but different values never collide on the same key
101
+ * (which would silently serve stale data — e.g. paginated search results,
102
+ * large object updates).
68
103
  */
69
104
  private generateCacheKey;
70
105
  /**
@@ -104,6 +139,15 @@ export declare class HttpService {
104
139
  getBaseURL(): string;
105
140
  clearCache(): void;
106
141
  clearCacheEntry(key: string): void;
142
+ /**
143
+ * Delete every cache entry whose key starts with `prefix`.
144
+ *
145
+ * Used by mutations that don't know the exact downstream cache keys —
146
+ * e.g. `updateProfile` invalidating all `GET:/session/user/*` entries
147
+ * without having to track every active session ID. Returns the number of
148
+ * deleted entries (for observability in tests).
149
+ */
150
+ clearCacheByPrefix(prefix: string): number;
107
151
  getCacheStats(): {
108
152
  size: number;
109
153
  hits: number;
@@ -45,6 +45,13 @@ export declare class OxyServicesBase {
45
45
  * Clear specific cache entry
46
46
  */
47
47
  clearCacheEntry(key: string): void;
48
+ /**
49
+ * Clear every cache entry whose key starts with `prefix`.
50
+ * Useful for mutations that invalidate a family of GET responses
51
+ * without enumerating each one (e.g. all session-user lookups after
52
+ * a profile update).
53
+ */
54
+ clearCacheByPrefix(prefix: string): number;
48
55
  /**
49
56
  * Get cache statistics
50
57
  */
@@ -63,8 +63,41 @@ import type { FedCMAuthOptions, FedCMConfig } from './mixins/OxyServices.fedcm';
63
63
  import type { PopupAuthOptions } from './mixins/OxyServices.popup';
64
64
  import type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
65
65
  import { composeOxyServices } from './mixins';
66
- declare const OxyServices_base: any;
67
- export declare class OxyServices extends OxyServices_base {
66
+ /**
67
+ * OxyServices - Unified client library for interacting with the Oxy API
68
+ *
69
+ * This class provides all API functionality in one simple, easy-to-use interface.
70
+ *
71
+ * ## Architecture
72
+ * - **HttpService**: Unified HTTP service handling authentication, caching, deduplication, queuing, and retry
73
+ * - **OxyServices**: Provides high-level API methods
74
+ *
75
+ * ## Mixin Composition
76
+ * The class is composed using TypeScript mixins for better code organization:
77
+ * - **Base**: Core infrastructure (HTTP client, request management, error handling)
78
+ * - **Auth**: Authentication and session management
79
+ * - **User**: User profiles, follow, notifications
80
+ * - **Privacy**: Blocked and restricted users
81
+ * - **Language**: Language detection and metadata
82
+ * - **Payment**: Payment processing
83
+ * - **Karma**: Karma system
84
+ * - **Assets**: File upload and asset management
85
+ * - **Developer**: Developer API management
86
+ * - **Location**: Location-based features
87
+ * - **Analytics**: Analytics tracking
88
+ * - **Devices**: Device management
89
+ * - **Utility**: Utility methods and Express middleware
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const oxy = new OxyServices({
94
+ * baseURL: 'https://api.oxy.so',
95
+ * cloudURL: 'https://cloud.oxy.so'
96
+ * });
97
+ * ```
98
+ */
99
+ declare const OxyServicesComposed: import("./mixins").ComposedOxyServicesConstructor;
100
+ export declare class OxyServices extends OxyServicesComposed {
68
101
  constructor(config: OxyConfig);
69
102
  }
70
103
  export interface OxyServices extends InstanceType<ReturnType<typeof composeOxyServices>> {
@@ -90,7 +123,7 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
90
123
  }
91
124
  export { OxyAuthenticationError, OxyAuthenticationTimeoutError };
92
125
  /**
93
- * Export the default Oxy Cloud URL (for backward compatibility)
126
+ * Default Oxy Cloud URL used when no `cloudURL` is provided to OxyServices.
94
127
  */
95
128
  export declare const OXY_CLOUD_URL = "https://cloud.oxy.so";
96
129
  /**
@@ -5,7 +5,7 @@
5
5
  * Handles key generation, secure storage, digital signatures, and recovery phrases.
6
6
  */
7
7
  import './polyfill';
8
- export { KeyManager, type KeyPair } from './keyManager';
8
+ export { KeyManager, IdentityAlreadyExistsError, IdentityPersistError, type KeyPair, } from './keyManager';
9
9
  export { SignatureService, type SignedMessage, type AuthChallenge } from './signatureService';
10
10
  export { RecoveryPhraseService, type RecoveryPhraseResult } from './recoveryPhrase';
11
11
  export { KeyManager as default } from './keyManager';
@@ -5,6 +5,31 @@
5
5
  * Private keys are stored securely using expo-secure-store and never leave the device.
6
6
  */
7
7
  import type { ECKeyPair } from 'elliptic';
8
+ /**
9
+ * Thrown when an identity-mutating operation (createIdentity / importKeyPair)
10
+ * is invoked while a valid identity already exists on the device.
11
+ *
12
+ * The local private key IS the user's identity — overwriting it without
13
+ * explicit consent permanently loses access to their account (unless
14
+ * they previously saved their recovery phrase). This error forces callers
15
+ * to make an explicit, audited decision instead of silently clobbering.
16
+ */
17
+ export declare class IdentityAlreadyExistsError extends Error {
18
+ readonly name = "IdentityAlreadyExistsError";
19
+ readonly existingPublicKey: string;
20
+ constructor(existingPublicKey: string);
21
+ }
22
+ /**
23
+ * Thrown when a freshly written identity cannot be read back, parsed, or
24
+ * round-tripped through sign/verify. Indicates a storage failure or
25
+ * corruption that would otherwise silently leave the user with an
26
+ * unusable account.
27
+ */
28
+ export declare class IdentityPersistError extends Error {
29
+ readonly cause?: unknown | undefined;
30
+ readonly name = "IdentityPersistError";
31
+ constructor(message: string, cause?: unknown | undefined);
32
+ }
8
33
  export interface KeyPair {
9
34
  publicKey: string;
10
35
  privateKey: string;
@@ -24,6 +49,19 @@ export declare class KeyManager {
24
49
  * Called internally when shared identity is created/deleted/imported
25
50
  */
26
51
  private static invalidateSharedCache;
52
+ /**
53
+ * Lowercase and pad to canonical 64-hex-char form.
54
+ *
55
+ * Tolerates the 1-in-256 leading-zero-strip that elliptic's
56
+ * `getPrivate('hex')` produces, and the externally-imported uppercase-hex
57
+ * legacy keys. EVERY `ec.keyFromPrivate(...)` call site in this file must
58
+ * canonicalize first so that derivation is stable regardless of storage
59
+ * representation.
60
+ *
61
+ * Private (used only inside KeyManager) — public consumers should not need
62
+ * to think about hex representation.
63
+ */
64
+ private static canonicalPrivateKey;
27
65
  /**
28
66
  * Generate a new ECDSA secp256k1 key pair
29
67
  * Returns the keys in hexadecimal format
@@ -120,14 +158,45 @@ export declare class KeyManager {
120
158
  */
121
159
  static migrateToSharedIdentity(): Promise<boolean>;
122
160
  /**
123
- * Generate and securely store a new key pair on the device
124
- * Returns only the public key (private key is stored securely)
161
+ * Atomically persist a key pair to secure storage with verification + backup.
162
+ *
163
+ * Write order is critical:
164
+ * 1. Backup (BACKUP_PRIVATE_KEY + BACKUP_PUBLIC_KEY + BACKUP_TIMESTAMP)
165
+ * 2. Primary public key
166
+ * 3. Primary private key (last so a partial write leaves us in a known
167
+ * "no identity yet" state — easier to retry than a half-written one)
168
+ * 4. Read back + sign/verify to confirm the storage round-trip works
169
+ *
170
+ * If any step throws, the caller sees the error AND any partial state is
171
+ * cleaned up so the device is left either fully consistent or fully empty.
172
+ * It never leaves an unusable half-identity that would fool `hasIdentity()`.
173
+ *
174
+ * @internal
125
175
  */
126
- static createIdentity(): Promise<string>;
176
+ private static _persistIdentityAtomic;
127
177
  /**
128
- * Import an existing key pair (e.g., from recovery phrase)
178
+ * Generate and securely store a new key pair on the device.
179
+ *
180
+ * Refuses to overwrite an existing identity unless `options.overwrite === true`.
181
+ * Returns the public key. The private key never leaves secure storage.
182
+ *
183
+ * @throws IdentityAlreadyExistsError if an identity already exists and overwrite is not set
184
+ * @throws IdentityPersistError if the key cannot be durably written
129
185
  */
130
- static importKeyPair(privateKey: string): Promise<string>;
186
+ static createIdentity(options?: {
187
+ overwrite?: boolean;
188
+ }): Promise<string>;
189
+ /**
190
+ * Import an existing key pair (e.g., from recovery phrase).
191
+ *
192
+ * Refuses to overwrite an existing identity unless `options.overwrite === true`.
193
+ *
194
+ * @throws IdentityAlreadyExistsError if an identity already exists and overwrite is not set
195
+ * @throws IdentityPersistError if the key cannot be durably written
196
+ */
197
+ static importKeyPair(privateKey: string, options?: {
198
+ overwrite?: boolean;
199
+ }): Promise<string>;
131
200
  /**
132
201
  * Get the stored private key
133
202
  * WARNING: Only use this for signing operations within the app
@@ -138,7 +207,15 @@ export declare class KeyManager {
138
207
  */
139
208
  static getPublicKey(): Promise<string | null>;
140
209
  /**
141
- * Check if an identity (key pair) exists on this device (cached for performance)
210
+ * Check if a complete, parseable identity exists on this device.
211
+ *
212
+ * Returns `true` only when BOTH the private and public keys are present,
213
+ * both are well-formed, AND the public key derives from the private key.
214
+ * A partially-written or corrupted identity returns `false` so that
215
+ * downstream code can resume the create / restore flow correctly.
216
+ *
217
+ * Note: this does NOT perform the full sign/verify roundtrip — call
218
+ * `verifyIdentityIntegrity()` for that.
142
219
  */
143
220
  static hasIdentity(): Promise<boolean>;
144
221
  /**
@@ -156,11 +233,26 @@ export declare class KeyManager {
156
233
  */
157
234
  static backupIdentity(): Promise<boolean>;
158
235
  /**
159
- * Verify identity integrity - checks if keys are valid and accessible
236
+ * Verify identity integrity checks keys are valid, accessible, derive
237
+ * consistently, AND can sign + verify a probe message.
238
+ *
239
+ * Returns true only when the full sign/verify roundtrip succeeds. Use
240
+ * this on app start to detect silent corruption before the user finds
241
+ * out by failing to sign in.
160
242
  */
161
243
  static verifyIdentityIntegrity(): Promise<boolean>;
162
244
  /**
163
- * Restore identity from backup if primary storage is corrupted
245
+ * Restore identity from backup if primary storage is corrupted.
246
+ *
247
+ * SAFETY: this method will NEVER overwrite a verifying primary identity.
248
+ * If the primary passes a sign/verify probe, the backup is left untouched
249
+ * and `false` is returned — this protects against a transient
250
+ * `verifyIdentityIntegrity()` blip clobbering valid keys with stale
251
+ * backup keys (e.g., from a previous account before an import).
252
+ *
253
+ * Additionally, if the backup public key does NOT match the (still-
254
+ * present-but-failing) primary public key, we refuse to overwrite — the
255
+ * backup may belong to a different identity entirely.
164
256
  */
165
257
  static restoreIdentityFromBackup(): Promise<boolean>;
166
258
  /**
@@ -174,10 +266,19 @@ export declare class KeyManager {
174
266
  static derivePublicKey(privateKey: string): string;
175
267
  /**
176
268
  * Validate that a string is a valid public key
269
+ *
270
+ * Returns false on parse errors (invalid input is the expected fail mode here).
271
+ * Errors are logged at debug level so they're available when troubleshooting
272
+ * but don't pollute production logs.
177
273
  */
178
274
  static isValidPublicKey(publicKey: string): boolean;
179
275
  /**
180
- * Validate that a string is a valid private key
276
+ * Validate that a string is a valid private key.
277
+ *
278
+ * secp256k1 private keys are 256-bit, so 64 hex chars. We require strict
279
+ * hex-only input because `elliptic`'s underlying `BN(input, 16)` happily
280
+ * accepts non-hex characters (treating them as zero), which would let
281
+ * "not-hex" pass through as a valid (but compromised, near-zero) key.
181
282
  */
182
283
  static isValidPrivateKey(privateKey: string): boolean;
183
284
  /**
@@ -5,7 +5,9 @@
5
5
  * across all platforms (Node.js, Browser, React Native).
6
6
  *
7
7
  * - Browser/Node.js: Uses native crypto
8
- * - React Native: Falls back to expo-crypto if native crypto unavailable
8
+ * - React Native: Uses expo-crypto (statically imported via the
9
+ * per-platform `platformCrypto` module — see that file's doc-comment for
10
+ * how platform routing works).
9
11
  */
10
12
  import { Buffer } from 'buffer';
11
13
  export { Buffer };
@@ -11,20 +11,44 @@ export interface RecoveryPhraseResult {
11
11
  words: string[];
12
12
  publicKey: string;
13
13
  }
14
+ export interface GenerateIdentityOptions {
15
+ /**
16
+ * Pass `true` to allow overwriting an existing on-device identity.
17
+ *
18
+ * Defaults to `false`. When false, this method throws
19
+ * `IdentityAlreadyExistsError` if a complete identity already exists,
20
+ * preventing accidental account loss. UI flows MUST only set this to
21
+ * `true` after explicitly confirming the user has saved their previous
22
+ * recovery phrase (or has otherwise been warned).
23
+ */
24
+ overwrite?: boolean;
25
+ }
14
26
  export declare class RecoveryPhraseService {
15
27
  /**
16
- * Generate a new identity with a recovery phrase
17
- * Returns the mnemonic phrase (should only be shown once to the user)
28
+ * Generate a new identity with a recovery phrase.
29
+ * The mnemonic phrase MUST be shown to the user exactly once after this
30
+ * call resolves — if it is lost, the account becomes unrecoverable.
31
+ *
32
+ * Refuses to overwrite an existing identity unless `options.overwrite === true`.
33
+ *
34
+ * @throws IdentityAlreadyExistsError if an identity already exists and overwrite is not set
18
35
  */
19
- static generateIdentityWithRecovery(): Promise<RecoveryPhraseResult>;
36
+ static generateIdentityWithRecovery(options?: GenerateIdentityOptions): Promise<RecoveryPhraseResult>;
20
37
  /**
21
- * Generate a 24-word recovery phrase for higher security
38
+ * Generate a 24-word recovery phrase for higher security.
39
+ *
40
+ * Same overwrite-protection semantics as `generateIdentityWithRecovery`.
22
41
  */
23
- static generateIdentityWithRecovery24(): Promise<RecoveryPhraseResult>;
42
+ static generateIdentityWithRecovery24(options?: GenerateIdentityOptions): Promise<RecoveryPhraseResult>;
24
43
  /**
25
- * Restore an identity from a recovery phrase
44
+ * Restore an identity from a recovery phrase.
45
+ *
46
+ * Refuses to overwrite a DIFFERENT existing identity unless
47
+ * `options.overwrite === true`. Re-importing the same phrase that
48
+ * matches the current identity is always allowed (it's a no-op refresh
49
+ * of the backup record).
26
50
  */
27
- static restoreFromPhrase(phrase: string): Promise<string>;
51
+ static restoreFromPhrase(phrase: string, options?: GenerateIdentityOptions): Promise<string>;
28
52
  /**
29
53
  * Validate a recovery phrase without importing it
30
54
  */
@@ -37,6 +37,10 @@ export declare class SignatureService {
37
37
  static signWithKey(message: string, privateKey: string): Promise<string>;
38
38
  /**
39
39
  * Verify a signature against a message and public key
40
+ *
41
+ * Returns false on any error (invalid signature, malformed input, etc.).
42
+ * Errors are logged at debug level so they're available when troubleshooting
43
+ * signature mismatches but don't surface to the caller.
40
44
  */
41
45
  static verify(message: string, signature: string, publicKey: string): Promise<boolean>;
42
46
  /**
@@ -26,7 +26,8 @@ export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
26
26
  export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
27
27
  export type { ServiceApp } from './mixins/OxyServices.utility';
28
28
  export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount } from './mixins/OxyServices.managedAccounts';
29
- export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto';
29
+ export type { ContactDiscoveryMatch, ContactDiscoveryResponse } from './mixins/OxyServices.contacts';
30
+ export { KeyManager, SignatureService, RecoveryPhraseService, IdentityAlreadyExistsError, IdentityPersistError, } from './crypto';
30
31
  export type { KeyPair, SignedMessage, AuthChallenge, RecoveryPhraseResult } from './crypto';
31
32
  export * from './models/interfaces';
32
33
  export * from './models/session';
@@ -57,7 +58,7 @@ export * from './utils/validationUtils';
57
58
  export { logger, LogLevel, logAuth, logApi, logSession, logUser, logDevice, logPayment, logPerformance, } from './utils/loggerUtils';
58
59
  export type { LogContext } from './utils/loggerUtils';
59
60
  export { updateAvatarVisibility } from './utils/avatarUtils';
60
- export { buildAccountsArray, createQuickAccount } from './utils/accountUtils';
61
- export type { QuickAccount } from './utils/accountUtils';
61
+ export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccountFallbackHandle, formatPublicKeyHandle, } from './utils/accountUtils';
62
+ export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
62
63
  import { OxyServices } from './OxyServices';
63
64
  export default OxyServices;
@@ -36,6 +36,7 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
36
36
  };
37
37
  clearCache(): void;
38
38
  clearCacheEntry(key: string): void;
39
+ clearCacheByPrefix(prefix: string): number;
39
40
  getCacheStats(): {
40
41
  size: number;
41
42
  hits: number;
@@ -1,4 +1,4 @@
1
- import type { AccountStorageUsageResponse, AssetUrlResponse, AssetVariant } from '../models/interfaces';
1
+ import type { AccountStorageUsageResponse, AssetUploadInput, AssetUrlResponse, AssetVariant } from '../models/interfaces';
2
2
  import type { OxyServicesBase } from '../OxyServices.base';
3
3
  export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T): {
4
4
  new (...args: any[]): {
@@ -45,7 +45,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
45
45
  /**
46
46
  * Upload raw file data
47
47
  */
48
- uploadRawFile(file: File | Blob, visibility?: "private" | "public" | "unlisted", metadata?: Record<string, any>): Promise<any>;
48
+ uploadRawFile(file: AssetUploadInput, visibility?: "private" | "public" | "unlisted", metadata?: Record<string, any>): Promise<any>;
49
49
  /**
50
50
  * Upload file using Central Asset Service.
51
51
  *
@@ -53,12 +53,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
53
53
  * ({uri, type, name, size}). RN descriptors are passed directly to
54
54
  * FormData.append, which handles them natively.
55
55
  */
56
- assetUpload(file: File | {
57
- uri: string;
58
- type?: string;
59
- name?: string;
60
- size?: number;
61
- }, visibility?: "private" | "public" | "unlisted", metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any>;
56
+ assetUpload(file: AssetUploadInput, visibility?: "private" | "public" | "unlisted", metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any>;
62
57
  /**
63
58
  * Link asset to an entity
64
59
  */
@@ -91,8 +86,8 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
91
86
  * Update asset visibility
92
87
  */
93
88
  assetUpdateVisibility(fileId: string, visibility: "private" | "public" | "unlisted"): Promise<any>;
94
- uploadAvatar(file: File, userId: string, app?: string): Promise<any>;
95
- uploadProfileBanner(file: File, userId: string, app?: string): Promise<any>;
89
+ uploadAvatar(file: AssetUploadInput, userId: string, app?: string): Promise<any>;
90
+ uploadProfileBanner(file: AssetUploadInput, userId: string, app?: string): Promise<any>;
96
91
  getAssetUrlCacheTTL(expiresIn?: number): number;
97
92
  fetchAssetDownloadUrl(fileId: string, variant?: string, cacheTTL?: number, expiresIn?: number): Promise<string | null>;
98
93
  fetchAssetContent(url: string, type: "text"): Promise<string>;
@@ -114,6 +109,7 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
114
109
  };
115
110
  clearCache(): void;
116
111
  clearCacheEntry(key: string): void;
112
+ clearCacheByPrefix(prefix: string): number;
117
113
  getCacheStats(): {
118
114
  size: number;
119
115
  hits: number;
@@ -40,6 +40,12 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
40
40
  /** @internal */ _serviceTokenExp: number;
41
41
  /** @internal */ _serviceApiKey: string | null;
42
42
  /** @internal */ _serviceApiSecret: string | null;
43
+ /**
44
+ * In-flight promise for service token fetch. Used to deduplicate concurrent
45
+ * calls to getServiceToken() — pattern mirrors AuthManager.refreshToken().
46
+ * @internal
47
+ */
48
+ _serviceTokenPromise: Promise<string> | null;
43
49
  /**
44
50
  * Configure service credentials for internal service-to-service communication.
45
51
  * Call this once at startup so that getServiceToken() and makeServiceRequest()
@@ -53,10 +59,19 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
53
59
  * Get a service token for internal service-to-service communication.
54
60
  * Tokens are short-lived (1h) and automatically cached/refreshed.
55
61
  *
62
+ * Concurrent callers share a single in-flight request to avoid hammering
63
+ * `/auth/service-token` when the cache is empty or expired.
64
+ *
56
65
  * @param apiKey - DeveloperApp API key (optional if configureServiceAuth was called)
57
66
  * @param apiSecret - DeveloperApp API secret (optional if configureServiceAuth was called)
58
67
  */
59
68
  getServiceToken(apiKey?: string, apiSecret?: string): Promise<string>;
69
+ /**
70
+ * Perform the actual /auth/service-token request and cache the result.
71
+ * Separated so getServiceToken() can deduplicate concurrent calls.
72
+ * @internal
73
+ */
74
+ _doFetchServiceToken(key: string, secret: string): Promise<string>;
60
75
  /**
61
76
  * Make an authenticated request on behalf of a user using a service token.
62
77
  * Automatically obtains/refreshes the service token.
@@ -192,6 +207,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
192
207
  };
193
208
  clearCache(): void;
194
209
  clearCacheEntry(key: string): void;
210
+ clearCacheByPrefix(prefix: string): number;
195
211
  getCacheStats(): {
196
212
  size: number;
197
213
  hits: number;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Contact Discovery Mixin
3
+ *
4
+ * Privacy-preserving discovery of which address-book contacts are on Oxy.
5
+ *
6
+ * The client hashes emails and phones locally before calling the API.
7
+ * The server responds with only Oxy user IDs and the hashes that matched,
8
+ * so the consumer can map each match back to the local contact that
9
+ * produced it.
10
+ *
11
+ * Hashing rules (must match the server `utils/contactHash.ts` exactly):
12
+ * - SHA-256, hex-encoded, lowercase
13
+ * - Email: `value.trim().toLowerCase()` then digest
14
+ * - Phone: trim → keep a single leading "+" → strip non-digits → prepend "+"
15
+ * if missing → digest
16
+ *
17
+ * Mobile clients can compute these digests with `expo-crypto`'s
18
+ * `digestStringAsync(SHA256, value, { encoding: HEX })`. Web clients should
19
+ * use `SubtleCrypto.digest('SHA-256', ...)`.
20
+ */
21
+ import type { OxyServicesBase } from '../OxyServices.base';
22
+ /** A single match returned by `POST /contacts/discover`. */
23
+ export interface ContactDiscoveryMatch {
24
+ /** Oxy user ID (MongoDB ObjectId hex string). */
25
+ userId: string;
26
+ /** The hashed identifier from the request that matched this user. */
27
+ hashedIdentifier: string;
28
+ /** Whether the match came from the email index or phone index. */
29
+ matchType: 'email' | 'phone';
30
+ }
31
+ /** Response shape of `POST /contacts/discover`. */
32
+ export interface ContactDiscoveryResponse {
33
+ matches: ContactDiscoveryMatch[];
34
+ }
35
+ export declare function OxyServicesContactsMixin<T extends typeof OxyServicesBase>(Base: T): {
36
+ new (...args: any[]): {
37
+ /**
38
+ * Discover which of the caller's contacts are on Oxy.
39
+ *
40
+ * @param hashedEmails - SHA-256 hex digests of normalized emails.
41
+ * @param hashedPhones - SHA-256 hex digests of normalized phone numbers.
42
+ * @returns Matches mapping each hashed identifier to the Oxy user ID it
43
+ * resolved to. Empty arrays are valid for either parameter, but at
44
+ * least one must be non-empty.
45
+ *
46
+ * The server enforces a 200-hash cap per channel per request — callers
47
+ * should batch larger address books client-side.
48
+ */
49
+ discoverContacts(hashedEmails: string[], hashedPhones: string[]): Promise<ContactDiscoveryResponse>;
50
+ httpService: import("../HttpService").HttpService;
51
+ cloudURL: string;
52
+ config: import("../OxyServices.base").OxyConfig;
53
+ __resetTokensForTests(): void;
54
+ makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
55
+ getBaseURL(): string;
56
+ getClient(): import("../HttpService").HttpService;
57
+ getMetrics(): {
58
+ totalRequests: number;
59
+ successfulRequests: number;
60
+ failedRequests: number;
61
+ cacheHits: number;
62
+ cacheMisses: number;
63
+ averageResponseTime: number;
64
+ };
65
+ clearCache(): void;
66
+ clearCacheEntry(key: string): void;
67
+ clearCacheByPrefix(prefix: string): number;
68
+ getCacheStats(): {
69
+ size: number;
70
+ hits: number;
71
+ misses: number;
72
+ hitRate: number;
73
+ };
74
+ getCloudURL(): string;
75
+ setTokens(accessToken: string, refreshToken?: string): void;
76
+ clearTokens(): void;
77
+ _cachedUserId: string | null | undefined;
78
+ _cachedAccessToken: string | null;
79
+ getCurrentUserId(): string | null;
80
+ hasValidToken(): boolean;
81
+ getAccessToken(): string | null;
82
+ setActingAs(userId: string | null): void;
83
+ getActingAs(): string | null;
84
+ waitForAuth(timeoutMs?: number): Promise<boolean>;
85
+ withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
86
+ maxRetries?: number;
87
+ retryDelay?: number;
88
+ authTimeoutMs?: number;
89
+ }): Promise<T_1>;
90
+ validate(): Promise<boolean>;
91
+ handleError(error: unknown): Error;
92
+ healthCheck(): Promise<{
93
+ status: string;
94
+ users?: number;
95
+ timestamp?: string;
96
+ [key: string]: any;
97
+ }>;
98
+ };
99
+ } & T;
@@ -69,6 +69,7 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
69
69
  };
70
70
  clearCache(): void;
71
71
  clearCacheEntry(key: string): void;
72
+ clearCacheByPrefix(prefix: string): number;
72
73
  getCacheStats(): {
73
74
  size: number;
74
75
  hits: number;
@@ -66,6 +66,7 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
66
66
  };
67
67
  clearCache(): void;
68
68
  clearCacheEntry(key: string): void;
69
+ clearCacheByPrefix(prefix: string): number;
69
70
  getCacheStats(): {
70
71
  size: number;
71
72
  hits: number;
@@ -177,10 +177,6 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
177
177
  * Get all available achievements
178
178
  */
179
179
  getAllAchievements(): Promise<Achievement[]>;
180
- /**
181
- * Delete user account (requires password confirmation)
182
- */
183
- deleteAccount(password: string): Promise<void>;
184
180
  httpService: import("../HttpService").HttpService;
185
181
  cloudURL: string;
186
182
  config: import("../OxyServices.base").OxyConfig;
@@ -198,6 +194,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
198
194
  };
199
195
  clearCache(): void;
200
196
  clearCacheEntry(key: string): void;
197
+ clearCacheByPrefix(prefix: string): number;
201
198
  getCacheStats(): {
202
199
  size: number;
203
200
  hits: number;
@@ -225,9 +222,7 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
225
222
  healthCheck(): Promise<{
226
223
  status: string;
227
224
  users?: number;
228
- timestamp? /**
229
- * Get FAQs
230
- */: string;
225
+ timestamp?: string;
231
226
  [key: string]: any;
232
227
  }>;
233
228
  };