@oxyhq/core 1.11.12 → 1.11.14
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 +214 -33
- 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 +10 -2
- package/dist/cjs/mixins/OxyServices.assets.js +9 -4
- package/dist/cjs/mixins/OxyServices.auth.js +147 -14
- 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 +59 -38
- package/dist/cjs/mixins/OxyServices.utility.js +416 -110
- package/dist/cjs/mixins/index.js +11 -3
- package/dist/cjs/utils/accountUtils.js +71 -2
- package/dist/cjs/utils/deviceManager.js +5 -36
- package/dist/cjs/utils/languageUtils.js +22 -0
- 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 +215 -34
- 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 +4 -3
- package/dist/esm/mixins/OxyServices.assets.js +9 -4
- package/dist/esm/mixins/OxyServices.auth.js +145 -14
- 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 +59 -38
- package/dist/esm/mixins/OxyServices.utility.js +416 -77
- package/dist/esm/mixins/index.js +11 -3
- package/dist/esm/utils/accountUtils.js +67 -1
- package/dist/esm/utils/deviceManager.js +5 -3
- package/dist/esm/utils/languageUtils.js +21 -0
- 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 +50 -7
- 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 +7 -5
- 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 +82 -5
- 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 +28 -11
- package/dist/types/mixins/OxyServices.utility.d.ts +145 -10
- 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/languageUtils.d.ts +1 -0
- package/dist/types/utils/platformCrypto.d.ts +87 -0
- package/dist/types/utils/platformCrypto.native.d.ts +54 -0
- package/package.json +45 -2
- package/src/CrossDomainAuth.ts +12 -10
- package/src/HttpService.ts +251 -40
- package/src/OxyServices.base.ts +10 -0
- package/src/OxyServices.ts +26 -7
- 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 -30
- package/src/i18n/locales/en-US.json +46 -1
- package/src/i18n/locales/es-ES.json +46 -1
- package/src/index.ts +19 -4
- package/src/mixins/OxyServices.assets.ts +15 -11
- package/src/mixins/OxyServices.auth.ts +175 -15
- 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 +72 -49
- package/src/mixins/OxyServices.utility.ts +562 -89
- package/src/mixins/__tests__/serviceAuth.test.ts +623 -0
- package/src/mixins/index.ts +58 -7
- package/src/models/interfaces.ts +65 -3
- package/src/utils/accountUtils.ts +82 -2
- package/src/utils/deviceManager.ts +7 -4
- package/src/utils/languageUtils.ts +23 -2
- 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>> {
|
|
@@ -79,24 +112,34 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
|
|
|
79
112
|
signUpWithRedirect(options?: RedirectAuthOptions): void;
|
|
80
113
|
auth(options?: {
|
|
81
114
|
debug?: boolean;
|
|
82
|
-
onError?: (error:
|
|
115
|
+
onError?: (error: unknown) => unknown;
|
|
83
116
|
loadUser?: boolean;
|
|
84
117
|
optional?: boolean;
|
|
85
|
-
|
|
118
|
+
jwtSecret?: string;
|
|
119
|
+
expectedIssuer?: string;
|
|
120
|
+
expectedAudience?: string;
|
|
121
|
+
}): (req: unknown, res: unknown, next: (err?: unknown) => void) => Promise<void>;
|
|
86
122
|
authSocket(options?: {
|
|
87
123
|
debug?: boolean;
|
|
88
|
-
}): (socket:
|
|
124
|
+
}): (socket: unknown, next: (err?: Error) => void) => Promise<void>;
|
|
125
|
+
serviceAuth(options?: {
|
|
126
|
+
debug?: boolean;
|
|
127
|
+
jwtSecret?: string;
|
|
128
|
+
expectedIssuer?: string;
|
|
129
|
+
expectedAudience?: string;
|
|
130
|
+
}): (req: unknown, res: unknown, next: (err?: unknown) => void) => Promise<void>;
|
|
131
|
+
requireScope(scope: string): (req: unknown, res: unknown, next: (err?: unknown) => void) => void;
|
|
89
132
|
assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<unknown>;
|
|
90
133
|
}
|
|
91
134
|
export { OxyAuthenticationError, OxyAuthenticationTimeoutError };
|
|
92
135
|
/**
|
|
93
|
-
*
|
|
136
|
+
* Default Oxy Cloud URL — used when no `cloudURL` is provided to OxyServices.
|
|
94
137
|
*/
|
|
95
138
|
export declare const OXY_CLOUD_URL = "https://cloud.oxy.so";
|
|
96
139
|
/**
|
|
97
140
|
* Export the default Oxy API URL (for documentation)
|
|
98
141
|
*/
|
|
99
|
-
export declare const OXY_API_URL:
|
|
142
|
+
export declare const OXY_API_URL: any;
|
|
100
143
|
/**
|
|
101
144
|
* Pre-configured client instance for easy import
|
|
102
145
|
* Uses OXY_API_URL as baseURL and OXY_CLOUD_URL as cloudURL
|
|
@@ -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
|
@@ -23,10 +23,12 @@ export type { CrossDomainAuthOptions } from './CrossDomainAuth';
|
|
|
23
23
|
export type { FedCMAuthOptions, FedCMConfig } from './mixins/OxyServices.fedcm';
|
|
24
24
|
export type { PopupAuthOptions } from './mixins/OxyServices.popup';
|
|
25
25
|
export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
|
|
26
|
+
export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth';
|
|
26
27
|
export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
|
|
27
|
-
export type { ServiceApp } from './mixins/OxyServices.utility';
|
|
28
|
+
export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
|
|
28
29
|
export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount } from './mixins/OxyServices.managedAccounts';
|
|
29
|
-
export {
|
|
30
|
+
export type { ContactDiscoveryMatch, ContactDiscoveryResponse } from './mixins/OxyServices.contacts';
|
|
31
|
+
export { KeyManager, SignatureService, RecoveryPhraseService, IdentityAlreadyExistsError, IdentityPersistError, } from './crypto';
|
|
30
32
|
export type { KeyPair, SignedMessage, AuthChallenge, RecoveryPhraseResult } from './crypto';
|
|
31
33
|
export * from './models/interfaces';
|
|
32
34
|
export * from './models/session';
|
|
@@ -34,7 +36,7 @@ export type { TopicData, TopicTranslation } from './models/Topic';
|
|
|
34
36
|
export { TopicType, TopicSource } from './models/Topic';
|
|
35
37
|
export { DeviceManager } from './utils/deviceManager';
|
|
36
38
|
export type { DeviceFingerprint, StoredDeviceInfo } from './utils/deviceManager';
|
|
37
|
-
export { SUPPORTED_LANGUAGES, getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode, } from './utils/languageUtils';
|
|
39
|
+
export { SUPPORTED_LANGUAGES, getLanguageMetadata, getLanguageName, getNativeLanguageName, normalizeLanguageCode, isRTLLocale, } from './utils/languageUtils';
|
|
38
40
|
export type { LanguageMetadata } from './utils/languageUtils';
|
|
39
41
|
export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, } from './utils/platform';
|
|
40
42
|
export type { PlatformOS } from './utils/platform';
|
|
@@ -57,7 +59,7 @@ export * from './utils/validationUtils';
|
|
|
57
59
|
export { logger, LogLevel, logAuth, logApi, logSession, logUser, logDevice, logPayment, logPerformance, } from './utils/loggerUtils';
|
|
58
60
|
export type { LogContext } from './utils/loggerUtils';
|
|
59
61
|
export { updateAvatarVisibility } from './utils/avatarUtils';
|
|
60
|
-
export { buildAccountsArray, createQuickAccount } from './utils/accountUtils';
|
|
61
|
-
export type { QuickAccount } from './utils/accountUtils';
|
|
62
|
+
export { buildAccountsArray, createQuickAccount, getAccountDisplayName, getAccountFallbackHandle, formatPublicKeyHandle, } from './utils/accountUtils';
|
|
63
|
+
export type { QuickAccount, DisplayNameUserShape } from './utils/accountUtils';
|
|
62
64
|
import { OxyServices } from './OxyServices';
|
|
63
65
|
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;
|
|
@@ -34,29 +34,104 @@ export interface ServiceTokenResponse {
|
|
|
34
34
|
expiresIn: number;
|
|
35
35
|
appName: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* One cache entry per (apiKey hash) → issued token + the secret that produced it.
|
|
39
|
+
* The secret is kept around in raw Buffer form so we can perform a
|
|
40
|
+
* constant-time compare against any reused credential pair — this prevents an
|
|
41
|
+
* attacker who learned a victim's apiKey from receiving the victim's cached
|
|
42
|
+
* service token by simply guessing the secret.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
interface ServiceTokenCacheEntry {
|
|
47
|
+
token: string;
|
|
48
|
+
/** Expiry as ms since epoch */
|
|
49
|
+
expiresAt: number;
|
|
50
|
+
/** Raw secret stored as Buffer for constant-time comparison on cache hit */
|
|
51
|
+
secretBuf: Buffer;
|
|
52
|
+
/** In-flight refresh promise (deduplicates concurrent callers) */
|
|
53
|
+
pending: Promise<string> | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sentinel error raised when getServiceToken() is called with a known apiKey
|
|
57
|
+
* but a non-matching secret. Indicates either credential drift in the caller
|
|
58
|
+
* or a cross-tenant cache lookup attempt. Surface as a 401-equivalent.
|
|
59
|
+
*/
|
|
60
|
+
export declare class ServiceCredentialMismatchError extends Error {
|
|
61
|
+
constructor();
|
|
62
|
+
}
|
|
37
63
|
export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
38
64
|
new (...args: any[]): {
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Per-credential token cache.
|
|
67
|
+
*
|
|
68
|
+
* Keyed by SHA-256(apiKey). Each entry carries:
|
|
69
|
+
* - the issued service JWT
|
|
70
|
+
* - its expiry timestamp
|
|
71
|
+
* - the secret that produced it (Buffer for constant-time compare)
|
|
72
|
+
* - an optional in-flight promise to deduplicate concurrent refreshes
|
|
73
|
+
*
|
|
74
|
+
* The previous implementation kept ONE token/exp pair per OxyServices
|
|
75
|
+
* instance. That meant calling `getServiceToken(keyA, secretA)` populated
|
|
76
|
+
* the cache, and a subsequent `getServiceToken(keyB, secretB)` (different
|
|
77
|
+
* tenant) would receive tenant A's token. This is fixed by routing every
|
|
78
|
+
* lookup through the Map.
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
_serviceTokenCache: Map<string, ServiceTokenCacheEntry>;
|
|
83
|
+
/** @internal Raw apiKey stored by configureServiceAuth() for use by getServiceToken() */
|
|
84
|
+
_serviceApiKey: string | null;
|
|
85
|
+
/** @internal Raw apiSecret stored by configureServiceAuth() for use by getServiceToken() */
|
|
86
|
+
_serviceApiSecret: string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Hash an apiKey into a stable Map cache key. Uses Node's SHA-256 — service
|
|
89
|
+
* tokens are only ever issued by a Node host (the SDK on web/RN never has
|
|
90
|
+
* the apiSecret in the first place), so we can rely on Node crypto here.
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
_hashApiKey(apiKey: string): Promise<string>;
|
|
43
95
|
/**
|
|
44
96
|
* Configure service credentials for internal service-to-service communication.
|
|
45
97
|
* Call this once at startup so that getServiceToken() and makeServiceRequest()
|
|
46
98
|
* can automatically obtain and refresh tokens.
|
|
47
99
|
*
|
|
100
|
+
* Calling this with credentials that differ from a previously-configured pair
|
|
101
|
+
* is allowed — each `(apiKey, apiSecret)` pair is cached independently, so
|
|
102
|
+
* legitimate multi-tenant hosts that need to switch credentials cannot leak
|
|
103
|
+
* one tenant's token to another tenant on the same instance.
|
|
104
|
+
*
|
|
48
105
|
* @param apiKey - DeveloperApp API key (oxy_dk_*)
|
|
49
106
|
* @param apiSecret - DeveloperApp API secret
|
|
50
107
|
*/
|
|
51
108
|
configureServiceAuth(apiKey: string, apiSecret: string): void;
|
|
52
109
|
/**
|
|
53
110
|
* Get a service token for internal service-to-service communication.
|
|
54
|
-
* Tokens are short-lived (1h) and automatically cached/refreshed
|
|
111
|
+
* Tokens are short-lived (1h) and automatically cached/refreshed per
|
|
112
|
+
* `(apiKey, apiSecret)` pair.
|
|
113
|
+
*
|
|
114
|
+
* Concurrent callers for the same credential pair share a single in-flight
|
|
115
|
+
* request to avoid hammering `/auth/service-token` when the cache is empty
|
|
116
|
+
* or expired.
|
|
117
|
+
*
|
|
118
|
+
* **Security guarantee:** if the cache already holds a token for this
|
|
119
|
+
* apiKey but the supplied apiSecret does not constant-time match the
|
|
120
|
+
* secret that originally produced that token, this method throws
|
|
121
|
+
* `ServiceCredentialMismatchError` instead of returning the cached token.
|
|
122
|
+
* This prevents an attacker who learned a peer's apiKey from extracting
|
|
123
|
+
* their service token by polling with a wrong secret.
|
|
55
124
|
*
|
|
56
125
|
* @param apiKey - DeveloperApp API key (optional if configureServiceAuth was called)
|
|
57
126
|
* @param apiSecret - DeveloperApp API secret (optional if configureServiceAuth was called)
|
|
58
127
|
*/
|
|
59
128
|
getServiceToken(apiKey?: string, apiSecret?: string): Promise<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Perform the actual /auth/service-token request and cache the result.
|
|
131
|
+
* Separated so getServiceToken() can deduplicate concurrent calls.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
_doFetchServiceToken(key: string, secret: string, cacheKey: string, secretBuf: Buffer): Promise<string>;
|
|
60
135
|
/**
|
|
61
136
|
* Make an authenticated request on behalf of a user using a service token.
|
|
62
137
|
* Automatically obtains/refreshes the service token.
|
|
@@ -192,6 +267,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
192
267
|
};
|
|
193
268
|
clearCache(): void;
|
|
194
269
|
clearCacheEntry(key: string): void;
|
|
270
|
+
clearCacheByPrefix(prefix: string): number;
|
|
195
271
|
getCacheStats(): {
|
|
196
272
|
size: number;
|
|
197
273
|
hits: number;
|
|
@@ -224,3 +300,4 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
224
300
|
}>;
|
|
225
301
|
};
|
|
226
302
|
} & T;
|
|
303
|
+
export {};
|