@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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/CrossDomainAuth.js +3 -1
- package/dist/cjs/HttpService.js +227 -51
- package/dist/cjs/OxyServices.base.js +9 -0
- package/dist/cjs/OxyServices.js +8 -3
- package/dist/cjs/crypto/index.js +3 -1
- package/dist/cjs/crypto/keyManager.js +476 -172
- package/dist/cjs/crypto/polyfill.js +14 -65
- package/dist/cjs/crypto/recoveryPhrase.js +30 -11
- package/dist/cjs/crypto/signatureService.js +25 -60
- package/dist/cjs/i18n/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/es-ES.json +46 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +46 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +46 -1
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/mixins/OxyServices.assets.js +9 -4
- package/dist/cjs/mixins/OxyServices.auth.js +27 -0
- package/dist/cjs/mixins/OxyServices.contacts.js +50 -0
- package/dist/cjs/mixins/OxyServices.features.js +0 -11
- package/dist/cjs/mixins/OxyServices.fedcm.js +4 -3
- package/dist/cjs/mixins/OxyServices.language.js +5 -36
- package/dist/cjs/mixins/OxyServices.redirect.js +6 -2
- package/dist/cjs/mixins/OxyServices.security.js +13 -2
- package/dist/cjs/mixins/OxyServices.user.js +70 -38
- package/dist/cjs/mixins/OxyServices.utility.js +19 -43
- package/dist/cjs/mixins/index.js +11 -3
- package/dist/cjs/utils/accountUtils.js +71 -2
- package/dist/cjs/utils/asyncUtils.js +34 -5
- package/dist/cjs/utils/deviceManager.js +5 -36
- package/dist/cjs/utils/platformCrypto.js +165 -0
- package/dist/cjs/utils/platformCrypto.native.js +123 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/CrossDomainAuth.js +3 -1
- package/dist/esm/HttpService.js +228 -52
- package/dist/esm/OxyServices.base.js +9 -0
- package/dist/esm/OxyServices.js +8 -3
- package/dist/esm/crypto/index.js +1 -1
- package/dist/esm/crypto/keyManager.js +473 -138
- package/dist/esm/crypto/polyfill.js +14 -32
- package/dist/esm/crypto/recoveryPhrase.js +30 -11
- package/dist/esm/crypto/signatureService.js +25 -27
- package/dist/esm/i18n/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/es-ES.json +46 -1
- package/dist/esm/i18n/locales/locales/en-US.json +46 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +46 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/mixins/OxyServices.assets.js +9 -4
- package/dist/esm/mixins/OxyServices.auth.js +27 -0
- package/dist/esm/mixins/OxyServices.contacts.js +47 -0
- package/dist/esm/mixins/OxyServices.features.js +0 -11
- package/dist/esm/mixins/OxyServices.fedcm.js +4 -3
- package/dist/esm/mixins/OxyServices.language.js +5 -3
- package/dist/esm/mixins/OxyServices.redirect.js +6 -2
- package/dist/esm/mixins/OxyServices.security.js +13 -2
- package/dist/esm/mixins/OxyServices.user.js +70 -38
- package/dist/esm/mixins/OxyServices.utility.js +19 -10
- package/dist/esm/mixins/index.js +11 -3
- package/dist/esm/utils/accountUtils.js +67 -1
- package/dist/esm/utils/asyncUtils.js +34 -5
- package/dist/esm/utils/deviceManager.js +5 -3
- package/dist/esm/utils/platformCrypto.js +125 -0
- package/dist/esm/utils/platformCrypto.native.js +80 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +47 -3
- package/dist/types/OxyServices.base.d.ts +7 -0
- package/dist/types/OxyServices.d.ts +36 -3
- package/dist/types/crypto/index.d.ts +1 -1
- package/dist/types/crypto/keyManager.d.ts +110 -9
- package/dist/types/crypto/polyfill.d.ts +3 -1
- package/dist/types/crypto/recoveryPhrase.d.ts +31 -7
- package/dist/types/crypto/signatureService.d.ts +4 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +6 -10
- package/dist/types/mixins/OxyServices.auth.d.ts +16 -0
- package/dist/types/mixins/OxyServices.contacts.d.ts +99 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +1 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -0
- package/dist/types/mixins/OxyServices.features.d.ts +2 -7
- package/dist/types/mixins/OxyServices.fedcm.d.ts +1 -0
- package/dist/types/mixins/OxyServices.karma.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.popup.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.security.d.ts +1 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -0
- package/dist/types/mixins/OxyServices.user.d.ts +40 -11
- package/dist/types/mixins/OxyServices.utility.d.ts +1 -0
- package/dist/types/mixins/index.d.ts +52 -4
- package/dist/types/models/interfaces.d.ts +62 -3
- package/dist/types/utils/accountUtils.d.ts +41 -1
- package/dist/types/utils/asyncUtils.d.ts +6 -2
- package/dist/types/utils/platformCrypto.d.ts +87 -0
- package/dist/types/utils/platformCrypto.native.d.ts +54 -0
- package/package.json +28 -1
- package/src/CrossDomainAuth.ts +12 -10
- package/src/HttpService.ts +264 -51
- package/src/OxyServices.base.ts +10 -0
- package/src/OxyServices.ts +9 -4
- package/src/crypto/__tests__/keyManager.test.ts +336 -0
- package/src/crypto/index.ts +6 -1
- package/src/crypto/keyManager.ts +529 -151
- package/src/crypto/polyfill.ts +14 -34
- package/src/crypto/recoveryPhrase.ts +56 -17
- package/src/crypto/signatureService.ts +25 -29
- package/src/i18n/locales/en-US.json +46 -1
- package/src/i18n/locales/es-ES.json +46 -1
- package/src/index.ts +16 -3
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.auth.ts +28 -0
- package/src/mixins/OxyServices.contacts.ts +73 -0
- package/src/mixins/OxyServices.features.ts +2 -12
- package/src/mixins/OxyServices.fedcm.ts +4 -3
- package/src/mixins/OxyServices.language.ts +6 -4
- package/src/mixins/OxyServices.redirect.ts +6 -2
- package/src/mixins/OxyServices.security.ts +18 -8
- package/src/mixins/OxyServices.user.ts +90 -49
- package/src/mixins/OxyServices.utility.ts +19 -10
- package/src/mixins/index.ts +58 -7
- package/src/models/interfaces.ts +65 -3
- package/src/utils/__tests__/asyncUtils.test.ts +187 -0
- package/src/utils/accountUtils.ts +82 -2
- package/src/utils/asyncUtils.ts +39 -9
- package/src/utils/deviceManager.ts +7 -4
- package/src/utils/platformCrypto.native.ts +101 -0
- 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
|
|
58
|
-
*
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
124
|
-
*
|
|
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
|
|
176
|
+
private static _persistIdentityAtomic;
|
|
127
177
|
/**
|
|
128
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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 {
|
|
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;
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
95
|
-
uploadProfileBanner(file:
|
|
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;
|
|
@@ -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
|
};
|