@oxyhq/services 5.13.12 → 5.13.16
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/README.md +10 -0
- package/lib/commonjs/core/OxyServices.base.js +271 -0
- package/lib/commonjs/core/OxyServices.base.js.map +1 -0
- package/lib/commonjs/core/OxyServices.errors.js +26 -0
- package/lib/commonjs/core/OxyServices.errors.js.map +1 -0
- package/lib/commonjs/core/OxyServices.js +58 -2009
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.analytics.js +60 -0
- package/lib/commonjs/core/mixins/OxyServices.analytics.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.assets.js +406 -0
- package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.auth.js +303 -0
- package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.developer.js +115 -0
- package/lib/commonjs/core/mixins/OxyServices.developer.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.devices.js +119 -0
- package/lib/commonjs/core/mixins/OxyServices.devices.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.karma.js +117 -0
- package/lib/commonjs/core/mixins/OxyServices.karma.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.language.js +124 -0
- package/lib/commonjs/core/mixins/OxyServices.language.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.location.js +55 -0
- package/lib/commonjs/core/mixins/OxyServices.location.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.payment.js +66 -0
- package/lib/commonjs/core/mixins/OxyServices.payment.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.privacy.js +174 -0
- package/lib/commonjs/core/mixins/OxyServices.privacy.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.totp.js +53 -0
- package/lib/commonjs/core/mixins/OxyServices.totp.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.user.js +389 -0
- package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -0
- package/lib/commonjs/core/mixins/OxyServices.utility.js +161 -0
- package/lib/commonjs/core/mixins/OxyServices.utility.js.map +1 -0
- package/lib/commonjs/core/mixins/index.js +39 -0
- package/lib/commonjs/core/mixins/index.js.map +1 -0
- package/lib/commonjs/core/mixins/mixinHelpers.js +62 -0
- package/lib/commonjs/core/mixins/mixinHelpers.js.map +1 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +26 -47
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +239 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/commonjs/utils/apiUtils.js +0 -14
- package/lib/commonjs/utils/apiUtils.js.map +1 -1
- package/lib/commonjs/utils/asyncUtils.js +0 -20
- package/lib/commonjs/utils/asyncUtils.js.map +1 -1
- package/lib/module/core/OxyServices.base.js +265 -0
- package/lib/module/core/OxyServices.base.js.map +1 -0
- package/lib/module/core/OxyServices.errors.js +20 -0
- package/lib/module/core/OxyServices.errors.js.map +1 -0
- package/lib/module/core/OxyServices.js +43 -2005
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.analytics.js +56 -0
- package/lib/module/core/mixins/OxyServices.analytics.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.assets.js +402 -0
- package/lib/module/core/mixins/OxyServices.assets.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.auth.js +299 -0
- package/lib/module/core/mixins/OxyServices.auth.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.developer.js +111 -0
- package/lib/module/core/mixins/OxyServices.developer.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.devices.js +115 -0
- package/lib/module/core/mixins/OxyServices.devices.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.karma.js +113 -0
- package/lib/module/core/mixins/OxyServices.karma.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.language.js +120 -0
- package/lib/module/core/mixins/OxyServices.language.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.location.js +51 -0
- package/lib/module/core/mixins/OxyServices.location.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.payment.js +62 -0
- package/lib/module/core/mixins/OxyServices.payment.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.privacy.js +170 -0
- package/lib/module/core/mixins/OxyServices.privacy.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.totp.js +49 -0
- package/lib/module/core/mixins/OxyServices.totp.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.user.js +385 -0
- package/lib/module/core/mixins/OxyServices.user.js.map +1 -0
- package/lib/module/core/mixins/OxyServices.utility.js +156 -0
- package/lib/module/core/mixins/OxyServices.utility.js.map +1 -0
- package/lib/module/core/mixins/index.js +36 -0
- package/lib/module/core/mixins/index.js.map +1 -0
- package/lib/module/core/mixins/mixinHelpers.js +56 -0
- package/lib/module/core/mixins/mixinHelpers.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +26 -47
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/navigation/types.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +241 -3
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/module/utils/apiUtils.js +0 -13
- package/lib/module/utils/apiUtils.js.map +1 -1
- package/lib/module/utils/asyncUtils.js +0 -20
- package/lib/module/utils/asyncUtils.js.map +1 -1
- package/lib/typescript/core/OxyServices.base.d.ts +123 -0
- package/lib/typescript/core/OxyServices.base.d.ts.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +969 -682
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/OxyServices.errors.d.ts +12 -0
- package/lib/typescript/core/OxyServices.errors.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.analytics.d.ts +70 -0
- package/lib/typescript/core/mixins/OxyServices.analytics.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts +159 -0
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +168 -0
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.developer.d.ts +103 -0
- package/lib/typescript/core/mixins/OxyServices.developer.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts +93 -0
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.karma.d.ts +89 -0
- package/lib/typescript/core/mixins/OxyServices.karma.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.language.d.ts +85 -0
- package/lib/typescript/core/mixins/OxyServices.language.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.location.d.ts +68 -0
- package/lib/typescript/core/mixins/OxyServices.location.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.payment.d.ts +74 -0
- package/lib/typescript/core/mixins/OxyServices.payment.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.privacy.d.ts +126 -0
- package/lib/typescript/core/mixins/OxyServices.privacy.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts +69 -0
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.user.d.ts +189 -0
- package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -0
- package/lib/typescript/core/mixins/OxyServices.utility.d.ts +97 -0
- package/lib/typescript/core/mixins/OxyServices.utility.d.ts.map +1 -0
- package/lib/typescript/core/mixins/index.d.ts +898 -0
- package/lib/typescript/core/mixins/index.d.ts.map +1 -0
- package/lib/typescript/core/mixins/mixinHelpers.d.ts +32 -0
- package/lib/typescript/core/mixins/mixinHelpers.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +36 -0
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +2 -6
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/types.d.ts +0 -1
- package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
- package/lib/typescript/utils/apiUtils.d.ts +0 -7
- package/lib/typescript/utils/apiUtils.d.ts.map +1 -1
- package/lib/typescript/utils/asyncUtils.d.ts +0 -11
- package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/OxyServices.base.ts +311 -0
- package/src/core/OxyServices.errors.ts +26 -0
- package/src/core/OxyServices.ts +43 -2026
- package/src/core/mixins/OxyServices.analytics.ts +53 -0
- package/src/core/mixins/OxyServices.assets.ts +390 -0
- package/src/core/mixins/OxyServices.auth.ts +275 -0
- package/src/core/mixins/OxyServices.developer.ts +114 -0
- package/src/core/mixins/OxyServices.devices.ts +103 -0
- package/src/core/mixins/OxyServices.karma.ts +111 -0
- package/src/core/mixins/OxyServices.language.ts +127 -0
- package/src/core/mixins/OxyServices.location.ts +46 -0
- package/src/core/mixins/OxyServices.payment.ts +59 -0
- package/src/core/mixins/OxyServices.privacy.ts +182 -0
- package/src/core/mixins/OxyServices.totp.ts +36 -0
- package/src/core/mixins/OxyServices.user.ts +380 -0
- package/src/core/mixins/OxyServices.utility.ts +187 -0
- package/src/core/mixins/index.ts +58 -0
- package/src/core/mixins/mixinHelpers.ts +69 -0
- package/src/index.ts +4 -0
- package/src/models/interfaces.ts +40 -0
- package/src/ui/context/OxyContext.tsx +35 -53
- package/src/ui/navigation/types.ts +0 -1
- package/src/ui/screens/PrivacySettingsScreen.tsx +240 -2
- package/src/utils/apiUtils.ts +0 -14
- package/src/utils/asyncUtils.ts +0 -20
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +0 -192
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +0 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +0 -142
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js +0 -113
- package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js.map +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js +0 -132
- package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js.map +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js +0 -83
- package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js.map +0 -1
- package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js +0 -58
- package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +0 -186
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +0 -136
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignUpIdentityStep.js +0 -108
- package/lib/module/ui/screens/internal/SignUpIdentityStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignUpSecurityStep.js +0 -127
- package/lib/module/ui/screens/internal/SignUpSecurityStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignUpSummaryStep.js +0 -78
- package/lib/module/ui/screens/internal/SignUpSummaryStep.js.map +0 -1
- package/lib/module/ui/screens/internal/SignUpWelcomeStep.js +0 -53
- package/lib/module/ui/screens/internal/SignUpWelcomeStep.js.map +0 -1
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +0 -28
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +0 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +0 -25
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +0 -1
- package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts +0 -20
- package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts.map +0 -1
- package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts +0 -24
- package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts.map +0 -1
- package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts +0 -15
- package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts.map +0 -1
- package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts +0 -13
- package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts.map +0 -1
- package/src/ui/screens/internal/SignInPasswordStep.tsx +0 -184
- package/src/ui/screens/internal/SignInUsernameStep.tsx +0 -145
- package/src/ui/screens/internal/SignUpIdentityStep.tsx +0 -112
- package/src/ui/screens/internal/SignUpSecurityStep.tsx +0 -132
- package/src/ui/screens/internal/SignUpSummaryStep.tsx +0 -66
- package/src/ui/screens/internal/SignUpWelcomeStep.tsx +0 -52
package/src/core/OxyServices.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* // Upload a file (browser File API)
|
|
21
21
|
* const fileInput = document.querySelector('input[type=file]');
|
|
22
22
|
* const file = fileInput.files[0];
|
|
23
|
-
* await oxy.
|
|
23
|
+
* await oxy.uploadRawFile(file);
|
|
24
24
|
*
|
|
25
25
|
* // Get a file stream URL for <img src>
|
|
26
26
|
* const url = oxy.getFileStreamUrl('fileId');
|
|
@@ -56,2043 +56,60 @@
|
|
|
56
56
|
*
|
|
57
57
|
* See method JSDoc for more details and options.
|
|
58
58
|
*/
|
|
59
|
-
import {
|
|
60
|
-
import
|
|
61
|
-
OxyConfig as OxyConfigBase,
|
|
62
|
-
ApiError,
|
|
63
|
-
User,
|
|
64
|
-
Notification,
|
|
65
|
-
AssetInitResponse,
|
|
66
|
-
AssetUrlResponse,
|
|
67
|
-
AssetVariant
|
|
68
|
-
} from '../models/interfaces';
|
|
69
|
-
import { normalizeLanguageCode, getLanguageMetadata, getLanguageName, getNativeLanguageName } from '../utils/languageUtils';
|
|
70
|
-
import type { LanguageMetadata } from '../utils/languageUtils';
|
|
71
|
-
/**
|
|
72
|
-
* OxyConfig - Configuration for OxyServices
|
|
73
|
-
* @property baseURL - The Oxy API base URL (e.g., https://api.oxy.so)
|
|
74
|
-
* @property cloudURL - The Oxy Cloud (file storage/CDN) URL (e.g., https://cloud.oxy.so)
|
|
75
|
-
*/
|
|
76
|
-
export interface OxyConfig extends OxyConfigBase {
|
|
77
|
-
cloudURL?: string;
|
|
78
|
-
}
|
|
79
|
-
import type { SessionLoginResponse } from '../models/session';
|
|
80
|
-
import { handleHttpError } from '../utils/errorUtils';
|
|
81
|
-
import { buildSearchParams, buildPaginationParams, type PaginationParams } from '../utils/apiUtils';
|
|
82
|
-
import { HttpClient } from './HttpClient';
|
|
83
|
-
import { RequestManager, type RequestOptions } from './RequestManager';
|
|
59
|
+
import { OxyServicesBase, type OxyConfig } from './OxyServices.base';
|
|
60
|
+
import { OxyAuthenticationError, OxyAuthenticationTimeoutError } from './OxyServices.errors';
|
|
84
61
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
userId?: string;
|
|
88
|
-
id?: string;
|
|
89
|
-
sessionId?: string;
|
|
90
|
-
[key: string]: any;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Custom error types for better error handling
|
|
95
|
-
*/
|
|
96
|
-
export class OxyAuthenticationError extends Error {
|
|
97
|
-
public readonly code: string;
|
|
98
|
-
public readonly status: number;
|
|
99
|
-
|
|
100
|
-
constructor(message: string, code = 'AUTH_ERROR', status = 401) {
|
|
101
|
-
super(message);
|
|
102
|
-
this.name = 'OxyAuthenticationError';
|
|
103
|
-
this.code = code;
|
|
104
|
-
this.status = status;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
|
|
109
|
-
constructor(operationName: string, timeoutMs: number) {
|
|
110
|
-
super(
|
|
111
|
-
`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`,
|
|
112
|
-
'AUTH_TIMEOUT',
|
|
113
|
-
408
|
|
114
|
-
);
|
|
115
|
-
this.name = 'OxyAuthenticationTimeoutError';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
62
|
+
// Import mixin composition helper
|
|
63
|
+
import { composeOxyServices } from './mixins';
|
|
118
64
|
|
|
119
65
|
/**
|
|
120
66
|
* OxyServices - Unified client library for interacting with the Oxy API
|
|
121
67
|
*
|
|
122
68
|
* This class provides all API functionality in one simple, easy-to-use interface.
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* -
|
|
126
|
-
* -
|
|
69
|
+
*
|
|
70
|
+
* ## Architecture
|
|
71
|
+
* - **HttpClient**: Handles HTTP communication and authentication
|
|
72
|
+
* - **RequestManager**: Handles 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
|
+
* - **TOTP**: Two-factor authentication enrollment
|
|
81
|
+
* - **Privacy**: Blocked and restricted users
|
|
82
|
+
* - **Language**: Language detection and metadata
|
|
83
|
+
* - **Payment**: Payment processing
|
|
84
|
+
* - **Karma**: Karma system
|
|
85
|
+
* - **Assets**: File upload and asset management
|
|
86
|
+
* - **Developer**: Developer API management
|
|
87
|
+
* - **Location**: Location-based features
|
|
88
|
+
* - **Analytics**: Analytics tracking
|
|
89
|
+
* - **Devices**: Device management
|
|
90
|
+
* - **Utility**: Utility methods and Express middleware
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const oxy = new OxyServices({
|
|
95
|
+
* baseURL: 'https://api.oxy.so',
|
|
96
|
+
* cloudURL: 'https://cloud.oxy.so'
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
127
99
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
private requestManager: RequestManager;
|
|
131
|
-
private cloudURL: string;
|
|
132
|
-
private config: OxyConfig;
|
|
133
|
-
|
|
100
|
+
// Compose all mixins into the final OxyServices class
|
|
101
|
+
const OxyServicesComposed = composeOxyServices();
|
|
134
102
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* @param config - Configuration for the client
|
|
138
|
-
* config.baseURL: Oxy API URL (e.g., https://api.oxy.so)
|
|
139
|
-
* config.cloudURL: Oxy Cloud URL (e.g., https://cloud.oxy.so)
|
|
140
|
-
*/
|
|
103
|
+
// Export as a named class to avoid TypeScript issues with anonymous class types
|
|
104
|
+
export class OxyServices extends OxyServicesComposed {
|
|
141
105
|
constructor(config: OxyConfig) {
|
|
142
|
-
|
|
143
|
-
this.cloudURL = config.cloudURL || OXY_CLOUD_URL;
|
|
144
|
-
|
|
145
|
-
// Initialize HTTP client (handles authentication and interceptors)
|
|
146
|
-
this.httpClient = new HttpClient(config);
|
|
147
|
-
|
|
148
|
-
// Initialize request manager (handles caching, deduplication, queuing, retry)
|
|
149
|
-
this.requestManager = new RequestManager(this.httpClient, config);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Test-only utility to reset global tokens between jest tests
|
|
153
|
-
static __resetTokensForTests(): void {
|
|
154
|
-
HttpClient.__resetTokensForTests();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Make a request with all performance optimizations
|
|
160
|
-
* This is the main method for all API calls - ensures authentication and performance features
|
|
161
|
-
*/
|
|
162
|
-
private async makeRequest<T>(
|
|
163
|
-
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
|
164
|
-
url: string,
|
|
165
|
-
data?: any,
|
|
166
|
-
options: RequestOptions = {}
|
|
167
|
-
): Promise<T> {
|
|
168
|
-
return this.requestManager.request<T>(method, url, data, options);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ============================================================================
|
|
172
|
-
// CORE METHODS (HTTP Client, Token Management, Error Handling)
|
|
173
|
-
// ============================================================================
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get the configured Oxy API base URL
|
|
177
|
-
*/
|
|
178
|
-
public getBaseURL(): string {
|
|
179
|
-
return this.httpClient.getBaseURL();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get the HTTP client instance
|
|
184
|
-
* Useful for advanced use cases where direct access to the HTTP client is needed
|
|
185
|
-
*/
|
|
186
|
-
public getClient(): HttpClient {
|
|
187
|
-
return this.httpClient;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get performance metrics
|
|
192
|
-
*/
|
|
193
|
-
public getMetrics() {
|
|
194
|
-
return this.requestManager.getMetrics();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Clear request cache
|
|
199
|
-
*/
|
|
200
|
-
public clearCache(): void {
|
|
201
|
-
this.requestManager.clearCache();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Clear specific cache entry
|
|
206
|
-
*/
|
|
207
|
-
public clearCacheEntry(key: string): void {
|
|
208
|
-
this.requestManager.clearCacheEntry(key);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Get cache statistics
|
|
213
|
-
*/
|
|
214
|
-
public getCacheStats() {
|
|
215
|
-
return this.requestManager.getCacheStats();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Get the configured Oxy Cloud (file storage/CDN) URL
|
|
220
|
-
*/
|
|
221
|
-
public getCloudURL(): string {
|
|
222
|
-
return this.cloudURL;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Set authentication tokens
|
|
227
|
-
*/
|
|
228
|
-
public setTokens(accessToken: string, refreshToken = ''): void {
|
|
229
|
-
this.httpClient.setTokens(accessToken, refreshToken);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Clear stored authentication tokens
|
|
234
|
-
*/
|
|
235
|
-
public clearTokens(): void {
|
|
236
|
-
this.httpClient.clearTokens();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Get the current user ID from the access token
|
|
241
|
-
*/
|
|
242
|
-
public getCurrentUserId(): string | null {
|
|
243
|
-
const accessToken = this.httpClient.getAccessToken();
|
|
244
|
-
if (!accessToken) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
try {
|
|
249
|
-
const decoded = jwtDecode<JwtPayload>(accessToken);
|
|
250
|
-
return decoded.userId || decoded.id || null;
|
|
251
|
-
} catch (error) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Check if the client has a valid access token
|
|
258
|
-
*/
|
|
259
|
-
private hasAccessToken(): boolean {
|
|
260
|
-
return this.httpClient.hasAccessToken();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Check if the client has a valid access token (public method)
|
|
265
|
-
*/
|
|
266
|
-
public hasValidToken(): boolean {
|
|
267
|
-
return this.httpClient.hasAccessToken();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Get the raw access token (for constructing anchor URLs when needed)
|
|
272
|
-
*/
|
|
273
|
-
public getAccessToken(): string | null {
|
|
274
|
-
return this.httpClient.getAccessToken();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Wait for authentication to be ready (public method)
|
|
279
|
-
* Useful for apps that want to ensure authentication is complete before proceeding
|
|
280
|
-
*/
|
|
281
|
-
public async waitForAuth(timeoutMs = 5000): Promise<boolean> {
|
|
282
|
-
return this.waitForAuthentication(timeoutMs);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Wait for authentication to be ready with timeout
|
|
287
|
-
*/
|
|
288
|
-
private async waitForAuthentication(timeoutMs = 5000): Promise<boolean> {
|
|
289
|
-
const startTime = Date.now();
|
|
290
|
-
const checkInterval = 100; // Check every 100ms
|
|
291
|
-
|
|
292
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
293
|
-
if (this.httpClient.hasAccessToken()) {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Execute a function with automatic authentication retry logic
|
|
304
|
-
* This handles the common case where API calls are made before authentication completes
|
|
305
|
-
*/
|
|
306
|
-
private async withAuthRetry<T>(
|
|
307
|
-
operation: () => Promise<T>,
|
|
308
|
-
operationName: string,
|
|
309
|
-
options: {
|
|
310
|
-
maxRetries?: number;
|
|
311
|
-
retryDelay?: number;
|
|
312
|
-
authTimeoutMs?: number;
|
|
313
|
-
} = {}
|
|
314
|
-
): Promise<T> {
|
|
315
|
-
const {
|
|
316
|
-
maxRetries = 2,
|
|
317
|
-
retryDelay = 1000,
|
|
318
|
-
authTimeoutMs = 5000
|
|
319
|
-
} = options;
|
|
320
|
-
|
|
321
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
322
|
-
try {
|
|
323
|
-
// First attempt: check if we have a token
|
|
324
|
-
if (!this.httpClient.hasAccessToken()) {
|
|
325
|
-
if (attempt === 0) {
|
|
326
|
-
// On first attempt, wait briefly for authentication to complete
|
|
327
|
-
const authReady = await this.waitForAuthentication(authTimeoutMs);
|
|
328
|
-
|
|
329
|
-
if (!authReady) {
|
|
330
|
-
throw new OxyAuthenticationTimeoutError(operationName, authTimeoutMs);
|
|
331
|
-
}
|
|
332
|
-
} else {
|
|
333
|
-
// On retry attempts, fail immediately if no token
|
|
334
|
-
throw new OxyAuthenticationError(
|
|
335
|
-
`Authentication required: ${operationName} requires a valid access token.`,
|
|
336
|
-
'AUTH_REQUIRED'
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Execute the operation
|
|
342
|
-
return await operation();
|
|
343
|
-
|
|
344
|
-
} catch (error: any) {
|
|
345
|
-
const isLastAttempt = attempt === maxRetries;
|
|
346
|
-
const isAuthError = error?.response?.status === 401 ||
|
|
347
|
-
error?.code === 'MISSING_TOKEN' ||
|
|
348
|
-
error?.message?.includes('Authentication') ||
|
|
349
|
-
error instanceof OxyAuthenticationError;
|
|
350
|
-
|
|
351
|
-
if (isAuthError && !isLastAttempt && !(error instanceof OxyAuthenticationTimeoutError)) {
|
|
352
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// If it's not an auth error, or it's the last attempt, throw the error
|
|
357
|
-
if (error instanceof OxyAuthenticationError) {
|
|
358
|
-
throw error;
|
|
359
|
-
}
|
|
360
|
-
throw this.handleError(error);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// This should never be reached, but TypeScript requires it
|
|
365
|
-
throw new OxyAuthenticationError(`${operationName} failed after ${maxRetries + 1} attempts`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Validate the current access token with the server
|
|
370
|
-
*/
|
|
371
|
-
async validate(): Promise<boolean> {
|
|
372
|
-
if (!this.hasAccessToken()) {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const res = await this.makeRequest<{ valid: boolean }>('GET', '/api/auth/validate', undefined, {
|
|
378
|
-
cache: false,
|
|
379
|
-
retry: false,
|
|
380
|
-
});
|
|
381
|
-
return res.valid === true;
|
|
382
|
-
} catch (error) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Centralized error handling
|
|
390
|
-
*/
|
|
391
|
-
protected handleError(error: any): Error {
|
|
392
|
-
const api = handleHttpError(error);
|
|
393
|
-
const err = new Error(api.message) as Error & { code?: string; status?: number; details?: Record<string, unknown> };
|
|
394
|
-
err.code = api.code;
|
|
395
|
-
err.status = api.status;
|
|
396
|
-
err.details = api.details as any;
|
|
397
|
-
return err;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Health check endpoint
|
|
402
|
-
*/
|
|
403
|
-
async healthCheck(): Promise<{
|
|
404
|
-
status: string;
|
|
405
|
-
users?: number;
|
|
406
|
-
timestamp?: string;
|
|
407
|
-
[key: string]: any
|
|
408
|
-
}> {
|
|
409
|
-
try {
|
|
410
|
-
return await this.makeRequest('GET', '/health', undefined, { cache: false });
|
|
411
|
-
} catch (error) {
|
|
412
|
-
throw this.handleError(error);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// ============================================================================
|
|
417
|
-
// AUTHENTICATION METHODS
|
|
418
|
-
// ============================================================================
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Sign up a new user
|
|
422
|
-
*/
|
|
423
|
-
async signUp(username: string, email: string, password: string): Promise<{ message: string; token: string; user: User }> {
|
|
424
|
-
try {
|
|
425
|
-
const res = await this.makeRequest<{ message: string; token: string; user: User }>('POST', '/api/auth/signup', {
|
|
426
|
-
username,
|
|
427
|
-
email,
|
|
428
|
-
password
|
|
429
|
-
}, { cache: false });
|
|
430
|
-
if (!res || (typeof res === 'object' && Object.keys(res).length === 0)) {
|
|
431
|
-
throw new OxyAuthenticationError('Sign up failed', 'SIGNUP_FAILED', 400);
|
|
432
|
-
}
|
|
433
|
-
return res;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
throw this.handleError(error);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Request account recovery (send verification code)
|
|
441
|
-
*/
|
|
442
|
-
async requestRecovery(identifier: string): Promise<{ delivery?: string; destination?: string }> {
|
|
443
|
-
try {
|
|
444
|
-
return await this.makeRequest('POST', '/api/auth/recover/request', { identifier }, { cache: false });
|
|
445
|
-
} catch (error: any) {
|
|
446
|
-
throw this.handleError(error);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Verify recovery code
|
|
452
|
-
*/
|
|
453
|
-
async verifyRecoveryCode(identifier: string, code: string): Promise<{ verified: boolean }> {
|
|
454
|
-
try {
|
|
455
|
-
return await this.makeRequest('POST', '/api/auth/recover/verify', { identifier, code }, { cache: false });
|
|
456
|
-
} catch (error: any) {
|
|
457
|
-
throw this.handleError(error);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Reset password using verified code
|
|
463
|
-
*/
|
|
464
|
-
async resetPassword(identifier: string, code: string, newPassword: string): Promise<{ success: boolean }> {
|
|
465
|
-
try {
|
|
466
|
-
return await this.makeRequest('POST', '/api/auth/recover/reset', { identifier, code, newPassword }, { cache: false });
|
|
467
|
-
} catch (error: any) {
|
|
468
|
-
throw this.handleError(error);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Reset password using TOTP code (recommended recovery)
|
|
474
|
-
*/
|
|
475
|
-
async resetPasswordWithTotp(identifier: string, code: string, newPassword: string): Promise<{ success: boolean }> {
|
|
476
|
-
try {
|
|
477
|
-
return await this.makeRequest('POST', '/api/auth/recover/totp/reset', { identifier, code, newPassword }, { cache: false });
|
|
478
|
-
} catch (error: any) {
|
|
479
|
-
throw this.handleError(error);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
async resetPasswordWithBackupCode(identifier: string, backupCode: string, newPassword: string): Promise<{ success: boolean }> {
|
|
484
|
-
try {
|
|
485
|
-
return await this.makeRequest('POST', '/api/auth/recover/backup/reset', { identifier, backupCode, newPassword }, { cache: false });
|
|
486
|
-
} catch (error: any) {
|
|
487
|
-
throw this.handleError(error);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async resetPasswordWithRecoveryKey(identifier: string, recoveryKey: string, newPassword: string): Promise<{ success: boolean; nextRecoveryKey?: string }> {
|
|
492
|
-
try {
|
|
493
|
-
return await this.makeRequest('POST', '/api/auth/recover/recovery-key/reset', { identifier, recoveryKey, newPassword }, { cache: false });
|
|
494
|
-
} catch (error: any) {
|
|
495
|
-
throw this.handleError(error);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Sign in with device management
|
|
501
|
-
*/
|
|
502
|
-
async signIn(
|
|
503
|
-
username: string,
|
|
504
|
-
password: string,
|
|
505
|
-
deviceName?: string,
|
|
506
|
-
deviceFingerprint?: any
|
|
507
|
-
): Promise<SessionLoginResponse | { mfaRequired: true; mfaToken: string; expiresAt: string }> {
|
|
508
|
-
try {
|
|
509
|
-
return await this.makeRequest<SessionLoginResponse | { mfaRequired: true; mfaToken: string; expiresAt: string }>('POST', '/api/auth/login', {
|
|
510
|
-
username,
|
|
511
|
-
password,
|
|
512
|
-
deviceName,
|
|
513
|
-
deviceFingerprint
|
|
514
|
-
}, { cache: false });
|
|
515
|
-
} catch (error) {
|
|
516
|
-
throw this.handleError(error);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Complete login by verifying TOTP with MFA token
|
|
522
|
-
*/
|
|
523
|
-
async verifyTotpLogin(mfaToken: string, code: string): Promise<SessionLoginResponse> {
|
|
524
|
-
try {
|
|
525
|
-
return await this.makeRequest<SessionLoginResponse>('POST', '/api/auth/totp/verify-login', { mfaToken, code }, { cache: false });
|
|
526
|
-
} catch (error) {
|
|
527
|
-
throw this.handleError(error);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Get user by session ID
|
|
533
|
-
*/
|
|
534
|
-
async getUserBySession(sessionId: string): Promise<User> {
|
|
535
|
-
try {
|
|
536
|
-
return await this.makeRequest<User>('GET', `/api/session/user/${sessionId}`, undefined, {
|
|
537
|
-
cache: true,
|
|
538
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache for user data
|
|
539
|
-
});
|
|
540
|
-
} catch (error) {
|
|
541
|
-
throw this.handleError(error);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Batch get multiple user profiles by session IDs (optimized for account switching)
|
|
547
|
-
* Returns array of { sessionId, user } objects
|
|
548
|
-
*/
|
|
549
|
-
async getUsersBySessions(sessionIds: string[]): Promise<Array<{ sessionId: string; user: User | null }>> {
|
|
550
|
-
try {
|
|
551
|
-
if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
|
|
552
|
-
return [];
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Deduplicate and sort sessionIds for consistent cache keys
|
|
556
|
-
const uniqueSessionIds = Array.from(new Set(sessionIds)).sort();
|
|
557
|
-
|
|
558
|
-
return await this.makeRequest<Array<{ sessionId: string; user: User | null }>>(
|
|
559
|
-
'POST',
|
|
560
|
-
'/api/session/users/batch',
|
|
561
|
-
{ sessionIds: uniqueSessionIds },
|
|
562
|
-
{
|
|
563
|
-
cache: true,
|
|
564
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
565
|
-
deduplicate: true, // Important for batch requests
|
|
566
|
-
}
|
|
567
|
-
);
|
|
568
|
-
} catch (error) {
|
|
569
|
-
throw this.handleError(error);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Get access token by session ID and set it in the token store
|
|
575
|
-
*/
|
|
576
|
-
async getTokenBySession(sessionId: string): Promise<{ accessToken: string; expiresAt: string }> {
|
|
577
|
-
try {
|
|
578
|
-
const res = await this.makeRequest<{ accessToken: string; expiresAt: string }>('GET', `/api/session/token/${sessionId}`, undefined, {
|
|
579
|
-
cache: false,
|
|
580
|
-
retry: false,
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// Set the token in the centralized token store
|
|
584
|
-
this.setTokens(res.accessToken);
|
|
585
|
-
|
|
586
|
-
return res;
|
|
587
|
-
} catch (error) {
|
|
588
|
-
throw this.handleError(error);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Get sessions by session ID
|
|
594
|
-
*/
|
|
595
|
-
async getSessionsBySessionId(sessionId: string): Promise<any[]> {
|
|
596
|
-
try {
|
|
597
|
-
return await this.makeRequest('GET', `/api/session/sessions/${sessionId}`, undefined, {
|
|
598
|
-
cache: false,
|
|
599
|
-
});
|
|
600
|
-
} catch (error) {
|
|
601
|
-
throw this.handleError(error);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Logout from a specific session
|
|
607
|
-
*/
|
|
608
|
-
async logoutSession(sessionId: string, targetSessionId?: string): Promise<void> {
|
|
609
|
-
try {
|
|
610
|
-
const url = targetSessionId
|
|
611
|
-
? `/api/session/logout/${sessionId}/${targetSessionId}`
|
|
612
|
-
: `/api/session/logout/${sessionId}`;
|
|
613
|
-
|
|
614
|
-
await this.makeRequest('POST', url, undefined, { cache: false });
|
|
615
|
-
} catch (error) {
|
|
616
|
-
throw this.handleError(error);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Logout from all sessions
|
|
622
|
-
*/
|
|
623
|
-
async logoutAllSessions(sessionId: string): Promise<void> {
|
|
624
|
-
try {
|
|
625
|
-
await this.makeRequest('POST', `/api/session/logout-all/${sessionId}`, undefined, { cache: false });
|
|
626
|
-
} catch (error) {
|
|
627
|
-
throw this.handleError(error);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Validate session
|
|
633
|
-
*/
|
|
634
|
-
async validateSession(
|
|
635
|
-
sessionId: string,
|
|
636
|
-
options: {
|
|
637
|
-
deviceFingerprint?: string;
|
|
638
|
-
useHeaderValidation?: boolean;
|
|
639
|
-
} = {}
|
|
640
|
-
): Promise<{
|
|
641
|
-
valid: boolean;
|
|
642
|
-
expiresAt: string;
|
|
643
|
-
lastActivity: string;
|
|
644
|
-
user: User;
|
|
645
|
-
sessionId?: string;
|
|
646
|
-
source?: string;
|
|
647
|
-
}> {
|
|
648
|
-
try {
|
|
649
|
-
const params = new URLSearchParams();
|
|
650
|
-
if (options.deviceFingerprint) {
|
|
651
|
-
params.append('deviceFingerprint', options.deviceFingerprint);
|
|
652
|
-
}
|
|
653
|
-
if (options.useHeaderValidation) {
|
|
654
|
-
params.append('useHeaderValidation', 'true');
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const url = `/api/session/validate/${sessionId}`;
|
|
658
|
-
const urlParams: any = {};
|
|
659
|
-
if (options.deviceFingerprint) urlParams.deviceFingerprint = options.deviceFingerprint;
|
|
660
|
-
if (options.useHeaderValidation) urlParams.useHeaderValidation = 'true';
|
|
661
|
-
return await this.makeRequest('GET', url, urlParams, { cache: false });
|
|
662
|
-
} catch (error) {
|
|
663
|
-
throw this.handleError(error);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Check username availability
|
|
669
|
-
*/
|
|
670
|
-
async checkUsernameAvailability(username: string): Promise<{ available: boolean; message: string }> {
|
|
671
|
-
try {
|
|
672
|
-
return await this.makeRequest('GET', `/api/auth/check-username/${username}`, undefined, { cache: false });
|
|
673
|
-
} catch (error) {
|
|
674
|
-
throw this.handleError(error);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Check email availability
|
|
680
|
-
*/
|
|
681
|
-
async checkEmailAvailability(email: string): Promise<{ available: boolean; message: string }> {
|
|
682
|
-
try {
|
|
683
|
-
return await this.makeRequest('GET', `/api/auth/check-email/${email}`, undefined, { cache: false });
|
|
684
|
-
} catch (error) {
|
|
685
|
-
throw this.handleError(error);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// ============================================================================
|
|
690
|
-
// USER METHODS
|
|
691
|
-
// ============================================================================
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Get profile by username
|
|
695
|
-
*/
|
|
696
|
-
async getProfileByUsername(username: string): Promise<User> {
|
|
697
|
-
try {
|
|
698
|
-
return await this.makeRequest<User>('GET', `/api/profiles/username/${username}`, undefined, {
|
|
699
|
-
cache: true,
|
|
700
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache for profiles
|
|
701
|
-
});
|
|
702
|
-
} catch (error) {
|
|
703
|
-
throw this.handleError(error);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// ============================================================================
|
|
708
|
-
// TOTP ENROLLMENT
|
|
709
|
-
// ============================================================================
|
|
710
|
-
|
|
711
|
-
async startTotpEnrollment(sessionId: string): Promise<{ secret: string; otpauthUrl: string; issuer: string; label: string }> {
|
|
712
|
-
try {
|
|
713
|
-
// Note: x-session-id header is handled by HttpClient interceptors if needed
|
|
714
|
-
return await this.makeRequest('POST', '/api/auth/totp/enroll/start', { sessionId }, { cache: false });
|
|
715
|
-
} catch (error) {
|
|
716
|
-
throw this.handleError(error);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
async verifyTotpEnrollment(sessionId: string, code: string): Promise<{ enabled: boolean; backupCodes?: string[]; recoveryKey?: string }> {
|
|
721
|
-
try {
|
|
722
|
-
return await this.makeRequest('POST', '/api/auth/totp/enroll/verify', { sessionId, code }, { cache: false });
|
|
723
|
-
} catch (error) {
|
|
724
|
-
throw this.handleError(error);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
async disableTotp(sessionId: string, code: string): Promise<{ disabled: boolean }> {
|
|
729
|
-
try {
|
|
730
|
-
return await this.makeRequest('POST', '/api/auth/totp/disable', { sessionId, code }, { cache: false });
|
|
731
|
-
} catch (error) {
|
|
732
|
-
throw this.handleError(error);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Search user profiles
|
|
738
|
-
*/
|
|
739
|
-
async searchProfiles(query: string, pagination?: PaginationParams): Promise<User[]> {
|
|
740
|
-
try {
|
|
741
|
-
const params = { query, ...pagination };
|
|
742
|
-
const searchParams = buildSearchParams(params);
|
|
743
|
-
const paramsObj: any = {};
|
|
744
|
-
searchParams.forEach((value, key) => {
|
|
745
|
-
paramsObj[key] = value;
|
|
746
|
-
});
|
|
747
|
-
return await this.makeRequest<User[]>('GET', '/api/profiles/search', paramsObj, {
|
|
748
|
-
cache: true,
|
|
749
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
750
|
-
});
|
|
751
|
-
} catch (error) {
|
|
752
|
-
throw this.handleError(error);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Get profile recommendations
|
|
758
|
-
*/
|
|
759
|
-
async getProfileRecommendations(): Promise<Array<{
|
|
760
|
-
id: string;
|
|
761
|
-
username: string;
|
|
762
|
-
name?: { first?: string; last?: string; full?: string };
|
|
763
|
-
description?: string;
|
|
764
|
-
_count?: { followers: number; following: number };
|
|
765
|
-
[key: string]: any;
|
|
766
|
-
}>> {
|
|
767
|
-
return this.withAuthRetry(async () => {
|
|
768
|
-
return await this.makeRequest('GET', '/api/profiles/recommendations', undefined, { cache: true });
|
|
769
|
-
}, 'getProfileRecommendations');
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Get user by ID
|
|
774
|
-
*/
|
|
775
|
-
async getUserById(userId: string): Promise<User> {
|
|
776
|
-
try {
|
|
777
|
-
return await this.makeRequest<User>('GET', `/api/users/${userId}`, undefined, {
|
|
778
|
-
cache: true,
|
|
779
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
780
|
-
});
|
|
781
|
-
} catch (error) {
|
|
782
|
-
throw this.handleError(error);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Get current user
|
|
788
|
-
*/
|
|
789
|
-
async getCurrentUser(): Promise<User> {
|
|
790
|
-
return this.withAuthRetry(async () => {
|
|
791
|
-
return await this.makeRequest<User>('GET', '/api/users/me', undefined, {
|
|
792
|
-
cache: true,
|
|
793
|
-
cacheTTL: 1 * 60 * 1000, // 1 minute cache for current user
|
|
794
|
-
});
|
|
795
|
-
}, 'getCurrentUser');
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* Update user profile
|
|
800
|
-
*/
|
|
801
|
-
async updateProfile(updates: Record<string, any>): Promise<User> {
|
|
802
|
-
try {
|
|
803
|
-
return await this.makeRequest<User>('PUT', '/api/users/me', updates, { cache: false });
|
|
804
|
-
} catch (error) {
|
|
805
|
-
throw this.handleError(error);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Get privacy settings for a user
|
|
811
|
-
* @param userId - The user ID (defaults to current user)
|
|
812
|
-
*/
|
|
813
|
-
async getPrivacySettings(userId?: string): Promise<any> {
|
|
814
|
-
try {
|
|
815
|
-
const id = userId || (await this.getCurrentUser()).id;
|
|
816
|
-
return await this.makeRequest<any>('GET', `/api/privacy/${id}/privacy`, undefined, {
|
|
817
|
-
cache: true,
|
|
818
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
819
|
-
});
|
|
820
|
-
} catch (error) {
|
|
821
|
-
throw this.handleError(error);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Update privacy settings
|
|
827
|
-
* @param settings - Partial privacy settings object
|
|
828
|
-
* @param userId - The user ID (defaults to current user)
|
|
829
|
-
*/
|
|
830
|
-
async updatePrivacySettings(settings: Record<string, any>, userId?: string): Promise<any> {
|
|
831
|
-
try {
|
|
832
|
-
const id = userId || (await this.getCurrentUser()).id;
|
|
833
|
-
return await this.makeRequest<any>('PATCH', `/api/privacy/${id}/privacy`, settings, {
|
|
834
|
-
cache: false,
|
|
835
|
-
});
|
|
836
|
-
} catch (error) {
|
|
837
|
-
throw this.handleError(error);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Request account verification
|
|
843
|
-
*/
|
|
844
|
-
async requestAccountVerification(reason: string, evidence?: string): Promise<{ message: string; requestId: string }> {
|
|
845
|
-
try {
|
|
846
|
-
return await this.makeRequest<{ message: string; requestId: string }>('POST', '/api/users/verify/request', {
|
|
847
|
-
reason,
|
|
848
|
-
evidence,
|
|
849
|
-
}, { cache: false });
|
|
850
|
-
} catch (error) {
|
|
851
|
-
throw this.handleError(error);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
* Download account data export
|
|
857
|
-
*/
|
|
858
|
-
async downloadAccountData(format: 'json' | 'csv' = 'json'): Promise<Blob> {
|
|
859
|
-
try {
|
|
860
|
-
// Use axios instance directly for blob responses since RequestManager doesn't handle blobs
|
|
861
|
-
const axiosInstance = this.httpClient.getAxiosInstance();
|
|
862
|
-
|
|
863
|
-
const response = await axiosInstance.get(`/api/users/me/data?format=${format}`, {
|
|
864
|
-
responseType: 'blob',
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
return response.data as Blob;
|
|
868
|
-
} catch (error) {
|
|
869
|
-
throw this.handleError(error);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Delete account permanently
|
|
875
|
-
* @param password - User password for confirmation
|
|
876
|
-
* @param confirmText - Confirmation text (usually username)
|
|
877
|
-
*/
|
|
878
|
-
async deleteAccount(password: string, confirmText: string): Promise<{ message: string }> {
|
|
879
|
-
try {
|
|
880
|
-
return await this.makeRequest<{ message: string }>('DELETE', '/api/users/me', {
|
|
881
|
-
password,
|
|
882
|
-
confirmText,
|
|
883
|
-
}, { cache: false });
|
|
884
|
-
} catch (error) {
|
|
885
|
-
throw this.handleError(error);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// ============================================================================
|
|
890
|
-
// LANGUAGE METHODS
|
|
891
|
-
// ============================================================================
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
* Get the current language from storage or user profile
|
|
895
|
-
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
896
|
-
* @returns The current language code (e.g., 'en-US') or null if not set
|
|
897
|
-
*/
|
|
898
|
-
async getCurrentLanguage(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
|
|
899
|
-
try {
|
|
900
|
-
// First try to get from user profile if authenticated
|
|
901
|
-
try {
|
|
902
|
-
const user = await this.getCurrentUser();
|
|
903
|
-
const userLanguage = (user as Record<string, unknown>)?.language as string | undefined;
|
|
904
|
-
if (userLanguage) {
|
|
905
|
-
return normalizeLanguageCode(userLanguage) || userLanguage;
|
|
906
|
-
}
|
|
907
|
-
} catch (e) {
|
|
908
|
-
// User not authenticated or error, continue to storage
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Fall back to storage
|
|
912
|
-
const storage = await this.getStorage();
|
|
913
|
-
const storageKey = `${storageKeyPrefix}_language`;
|
|
914
|
-
const storedLanguage = await storage.getItem(storageKey);
|
|
915
|
-
if (storedLanguage) {
|
|
916
|
-
return normalizeLanguageCode(storedLanguage) || storedLanguage;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
return null;
|
|
920
|
-
} catch (error) {
|
|
921
|
-
if (__DEV__) {
|
|
922
|
-
console.warn('Failed to get current language:', error);
|
|
923
|
-
}
|
|
924
|
-
return null;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Get the current language with metadata (name, nativeName, etc.)
|
|
930
|
-
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
931
|
-
* @returns Language metadata object or null if not set
|
|
932
|
-
*/
|
|
933
|
-
async getCurrentLanguageMetadata(storageKeyPrefix: string = 'oxy_session'): Promise<LanguageMetadata | null> {
|
|
934
|
-
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
935
|
-
return getLanguageMetadata(languageCode);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/**
|
|
939
|
-
* Get the current language name (e.g., 'English')
|
|
940
|
-
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
941
|
-
* @returns Language name or null if not set
|
|
942
|
-
*/
|
|
943
|
-
async getCurrentLanguageName(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
|
|
944
|
-
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
945
|
-
if (!languageCode) return null;
|
|
946
|
-
return getLanguageName(languageCode);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Get the current native language name (e.g., 'Español')
|
|
951
|
-
* @param storageKeyPrefix - Optional prefix for storage key (default: 'oxy_session')
|
|
952
|
-
* @returns Native language name or null if not set
|
|
953
|
-
*/
|
|
954
|
-
async getCurrentNativeLanguageName(storageKeyPrefix: string = 'oxy_session'): Promise<string | null> {
|
|
955
|
-
const languageCode = await this.getCurrentLanguage(storageKeyPrefix);
|
|
956
|
-
if (!languageCode) return null;
|
|
957
|
-
return getNativeLanguageName(languageCode);
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Get appropriate storage for the platform (similar to DeviceManager)
|
|
962
|
-
* @private
|
|
963
|
-
*/
|
|
964
|
-
private async getStorage(): Promise<{
|
|
965
|
-
getItem: (key: string) => Promise<string | null>;
|
|
966
|
-
setItem: (key: string, value: string) => Promise<void>;
|
|
967
|
-
removeItem: (key: string) => Promise<void>;
|
|
968
|
-
}> {
|
|
969
|
-
const isReactNative = typeof navigator !== 'undefined' && navigator.product === 'ReactNative';
|
|
970
|
-
|
|
971
|
-
if (isReactNative) {
|
|
972
|
-
try {
|
|
973
|
-
const asyncStorageModule = await import('@react-native-async-storage/async-storage');
|
|
974
|
-
const storage = (asyncStorageModule.default as unknown) as import('@react-native-async-storage/async-storage').AsyncStorageStatic;
|
|
975
|
-
return {
|
|
976
|
-
getItem: storage.getItem.bind(storage),
|
|
977
|
-
setItem: storage.setItem.bind(storage),
|
|
978
|
-
removeItem: storage.removeItem.bind(storage),
|
|
979
|
-
};
|
|
980
|
-
} catch (error) {
|
|
981
|
-
console.error('AsyncStorage not available in React Native:', error);
|
|
982
|
-
throw new Error('AsyncStorage is required in React Native environment');
|
|
983
|
-
}
|
|
984
|
-
} else {
|
|
985
|
-
// Use localStorage for web
|
|
986
|
-
return {
|
|
987
|
-
getItem: async (key: string) => {
|
|
988
|
-
if (typeof window !== 'undefined' && window.localStorage) {
|
|
989
|
-
return localStorage.getItem(key);
|
|
990
|
-
}
|
|
991
|
-
return null;
|
|
992
|
-
},
|
|
993
|
-
setItem: async (key: string, value: string) => {
|
|
994
|
-
if (typeof window !== 'undefined' && window.localStorage) {
|
|
995
|
-
localStorage.setItem(key, value);
|
|
996
|
-
}
|
|
997
|
-
},
|
|
998
|
-
removeItem: async (key: string) => {
|
|
999
|
-
if (typeof window !== 'undefined' && window.localStorage) {
|
|
1000
|
-
localStorage.removeItem(key);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
/**
|
|
1008
|
-
* Update user by ID (admin function)
|
|
1009
|
-
*/
|
|
1010
|
-
async updateUser(userId: string, updates: Record<string, any>): Promise<User> {
|
|
1011
|
-
try {
|
|
1012
|
-
return await this.makeRequest<User>('PUT', `/api/users/${userId}`, updates, { cache: false });
|
|
1013
|
-
} catch (error) {
|
|
1014
|
-
throw this.handleError(error);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Follow a user
|
|
1020
|
-
*/
|
|
1021
|
-
async followUser(userId: string): Promise<{ success: boolean; message: string }> {
|
|
1022
|
-
try {
|
|
1023
|
-
return await this.makeRequest('POST', `/api/users/${userId}/follow`, undefined, { cache: false });
|
|
1024
|
-
} catch (error) {
|
|
1025
|
-
throw this.handleError(error);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
* Unfollow a user
|
|
1031
|
-
*/
|
|
1032
|
-
async unfollowUser(userId: string): Promise<{ success: boolean; message: string }> {
|
|
1033
|
-
try {
|
|
1034
|
-
return await this.makeRequest('DELETE', `/api/users/${userId}/follow`, undefined, { cache: false });
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
throw this.handleError(error);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Get follow status
|
|
1042
|
-
*/
|
|
1043
|
-
async getFollowStatus(userId: string): Promise<{ isFollowing: boolean }> {
|
|
1044
|
-
try {
|
|
1045
|
-
return await this.makeRequest('GET', `/api/users/${userId}/follow-status`, undefined, {
|
|
1046
|
-
cache: true,
|
|
1047
|
-
cacheTTL: 1 * 60 * 1000, // 1 minute cache
|
|
1048
|
-
});
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
throw this.handleError(error);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Get user followers
|
|
1056
|
-
*/
|
|
1057
|
-
async getUserFollowers(
|
|
1058
|
-
userId: string,
|
|
1059
|
-
pagination?: PaginationParams
|
|
1060
|
-
): Promise<{ followers: User[]; total: number; hasMore: boolean }> {
|
|
1061
|
-
try {
|
|
1062
|
-
const params = buildPaginationParams(pagination || {});
|
|
1063
|
-
const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/followers`, params, {
|
|
1064
|
-
cache: true,
|
|
1065
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1066
|
-
});
|
|
1067
|
-
return {
|
|
1068
|
-
followers: response.data || [],
|
|
1069
|
-
total: response.pagination.total,
|
|
1070
|
-
hasMore: response.pagination.hasMore,
|
|
1071
|
-
};
|
|
1072
|
-
} catch (error) {
|
|
1073
|
-
throw this.handleError(error);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* Get user following
|
|
1079
|
-
*/
|
|
1080
|
-
async getUserFollowing(
|
|
1081
|
-
userId: string,
|
|
1082
|
-
pagination?: PaginationParams
|
|
1083
|
-
): Promise<{ following: User[]; total: number; hasMore: boolean }> {
|
|
1084
|
-
try {
|
|
1085
|
-
const params = buildPaginationParams(pagination || {});
|
|
1086
|
-
const response = await this.makeRequest<{ data: User[]; pagination: { total: number; hasMore: boolean } }>('GET', `/api/users/${userId}/following`, params, {
|
|
1087
|
-
cache: true,
|
|
1088
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1089
|
-
});
|
|
1090
|
-
return {
|
|
1091
|
-
following: response.data || [],
|
|
1092
|
-
total: response.pagination.total,
|
|
1093
|
-
hasMore: response.pagination.hasMore,
|
|
1094
|
-
};
|
|
1095
|
-
} catch (error) {
|
|
1096
|
-
throw this.handleError(error);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
/**
|
|
1101
|
-
* Get notifications
|
|
1102
|
-
*/
|
|
1103
|
-
async getNotifications(): Promise<Notification[]> {
|
|
1104
|
-
return this.withAuthRetry(async () => {
|
|
1105
|
-
return await this.makeRequest<Notification[]>('GET', '/api/notifications', undefined, {
|
|
1106
|
-
cache: false, // Don't cache notifications - always get fresh data
|
|
1107
|
-
});
|
|
1108
|
-
}, 'getNotifications');
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
/**
|
|
1112
|
-
* Get unread notification count
|
|
1113
|
-
*/
|
|
1114
|
-
async getUnreadCount(): Promise<number> {
|
|
1115
|
-
try {
|
|
1116
|
-
const res = await this.makeRequest<{ count: number }>('GET', '/api/notifications/unread-count', undefined, {
|
|
1117
|
-
cache: false, // Don't cache unread count - always get fresh data
|
|
1118
|
-
});
|
|
1119
|
-
return res.count;
|
|
1120
|
-
} catch (error) {
|
|
1121
|
-
throw this.handleError(error);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
/**
|
|
1126
|
-
* Create notification
|
|
1127
|
-
*/
|
|
1128
|
-
async createNotification(data: Partial<Notification>): Promise<Notification> {
|
|
1129
|
-
try {
|
|
1130
|
-
return await this.makeRequest<Notification>('POST', '/api/notifications', data, { cache: false });
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
throw this.handleError(error);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
/**
|
|
1137
|
-
* Mark notification as read
|
|
1138
|
-
*/
|
|
1139
|
-
async markNotificationAsRead(notificationId: string): Promise<void> {
|
|
1140
|
-
try {
|
|
1141
|
-
await this.makeRequest('PUT', `/api/notifications/${notificationId}/read`, undefined, { cache: false });
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
throw this.handleError(error);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* Mark all notifications as read
|
|
1149
|
-
*/
|
|
1150
|
-
async markAllNotificationsAsRead(): Promise<void> {
|
|
1151
|
-
try {
|
|
1152
|
-
await this.makeRequest('PUT', '/api/notifications/read-all', undefined, { cache: false });
|
|
1153
|
-
} catch (error) {
|
|
1154
|
-
throw this.handleError(error);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
/**
|
|
1159
|
-
* Delete notification
|
|
1160
|
-
*/
|
|
1161
|
-
async deleteNotification(notificationId: string): Promise<void> {
|
|
1162
|
-
try {
|
|
1163
|
-
await this.makeRequest('DELETE', `/api/notifications/${notificationId}`, undefined, { cache: false });
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
throw this.handleError(error);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// ============================================================================
|
|
1170
|
-
// PAYMENT METHODS
|
|
1171
|
-
// ============================================================================
|
|
1172
|
-
|
|
1173
|
-
/**
|
|
1174
|
-
* Create a payment
|
|
1175
|
-
*/
|
|
1176
|
-
async createPayment(data: any): Promise<any> {
|
|
1177
|
-
try {
|
|
1178
|
-
return await this.makeRequest('POST', '/api/payments', data, { cache: false });
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
throw this.handleError(error);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
/**
|
|
1185
|
-
* Get payment by ID
|
|
1186
|
-
*/
|
|
1187
|
-
async getPayment(paymentId: string): Promise<any> {
|
|
1188
|
-
try {
|
|
1189
|
-
return await this.makeRequest('GET', `/api/payments/${paymentId}`, undefined, {
|
|
1190
|
-
cache: true,
|
|
1191
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
1192
|
-
});
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
throw this.handleError(error);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Get user payments
|
|
1200
|
-
*/
|
|
1201
|
-
async getUserPayments(): Promise<any[]> {
|
|
1202
|
-
try {
|
|
1203
|
-
return await this.makeRequest('GET', '/api/payments/user', undefined, {
|
|
1204
|
-
cache: false, // Don't cache user payments - always get fresh data
|
|
1205
|
-
});
|
|
1206
|
-
} catch (error) {
|
|
1207
|
-
throw this.handleError(error);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// ============================================================================
|
|
1212
|
-
// KARMA METHODS
|
|
1213
|
-
// ============================================================================
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* Get user karma
|
|
1217
|
-
*/
|
|
1218
|
-
async getUserKarma(userId: string): Promise<any> {
|
|
1219
|
-
try {
|
|
1220
|
-
return await this.makeRequest('GET', `/api/karma/${userId}`, undefined, {
|
|
1221
|
-
cache: true,
|
|
1222
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1223
|
-
});
|
|
1224
|
-
} catch (error) {
|
|
1225
|
-
throw this.handleError(error);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
/**
|
|
1230
|
-
* Give karma to user
|
|
1231
|
-
*/
|
|
1232
|
-
async giveKarma(userId: string, amount: number, reason?: string): Promise<any> {
|
|
1233
|
-
try {
|
|
1234
|
-
return await this.makeRequest('POST', `/api/karma/${userId}/give`, {
|
|
1235
|
-
amount,
|
|
1236
|
-
reason
|
|
1237
|
-
}, { cache: false });
|
|
1238
|
-
} catch (error) {
|
|
1239
|
-
throw this.handleError(error);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
/**
|
|
1244
|
-
* Get user karma total
|
|
1245
|
-
*/
|
|
1246
|
-
async getUserKarmaTotal(userId: string): Promise<any> {
|
|
1247
|
-
try {
|
|
1248
|
-
return await this.makeRequest('GET', `/api/karma/${userId}/total`, undefined, {
|
|
1249
|
-
cache: true,
|
|
1250
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1251
|
-
});
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
throw this.handleError(error);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
/**
|
|
1258
|
-
* Get user karma history
|
|
1259
|
-
*/
|
|
1260
|
-
async getUserKarmaHistory(userId: string, limit?: number, offset?: number): Promise<any> {
|
|
1261
|
-
try {
|
|
1262
|
-
const params: any = {};
|
|
1263
|
-
if (limit) params.limit = limit;
|
|
1264
|
-
if (offset) params.offset = offset;
|
|
1265
|
-
|
|
1266
|
-
return await this.makeRequest('GET', `/api/karma/${userId}/history`, params, {
|
|
1267
|
-
cache: true,
|
|
1268
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1269
|
-
});
|
|
1270
|
-
} catch (error) {
|
|
1271
|
-
throw this.handleError(error);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
/**
|
|
1276
|
-
* Get karma leaderboard
|
|
1277
|
-
*/
|
|
1278
|
-
async getKarmaLeaderboard(): Promise<any> {
|
|
1279
|
-
try {
|
|
1280
|
-
return await this.makeRequest('GET', '/api/karma/leaderboard', undefined, {
|
|
1281
|
-
cache: true,
|
|
1282
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
1283
|
-
});
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
throw this.handleError(error);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* Get karma rules
|
|
1291
|
-
*/
|
|
1292
|
-
async getKarmaRules(): Promise<any> {
|
|
1293
|
-
try {
|
|
1294
|
-
return await this.makeRequest('GET', '/api/karma/rules', undefined, {
|
|
1295
|
-
cache: true,
|
|
1296
|
-
cacheTTL: 30 * 60 * 1000, // 30 minutes cache (rules don't change often)
|
|
1297
|
-
});
|
|
1298
|
-
} catch (error) {
|
|
1299
|
-
throw this.handleError(error);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// ============================================================================
|
|
1304
|
-
// FILE METHODS (LEGACY - Using Asset Service)
|
|
1305
|
-
// ============================================================================
|
|
1306
|
-
|
|
1307
|
-
/**
|
|
1308
|
-
* Delete file
|
|
1309
|
-
*/
|
|
1310
|
-
async deleteFile(fileId: string): Promise<any> {
|
|
1311
|
-
try {
|
|
1312
|
-
// Central Asset Service delete with force=true behavior controlled by caller via assetDelete
|
|
1313
|
-
return await this.makeRequest('DELETE', `/api/assets/${encodeURIComponent(fileId)}`, undefined, { cache: false });
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
throw this.handleError(error);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
* Get file download URL (API streaming proxy, attaches token for <img src>)
|
|
1321
|
-
*/
|
|
1322
|
-
getFileDownloadUrl(fileId: string, variant?: string, expiresIn?: number): string {
|
|
1323
|
-
const base = this.getBaseURL();
|
|
1324
|
-
const params = new URLSearchParams();
|
|
1325
|
-
if (variant) params.set('variant', variant);
|
|
1326
|
-
if (expiresIn) params.set('expiresIn', String(expiresIn));
|
|
1327
|
-
params.set('fallback', 'placeholderVisible');
|
|
1328
|
-
const token = this.httpClient.getAccessToken();
|
|
1329
|
-
if (token) params.set('token', token);
|
|
1330
|
-
|
|
1331
|
-
// Use params.toString() to detect whether there are query params.
|
|
1332
|
-
// URLSearchParams.size is not a standard property across all JS runtimes
|
|
1333
|
-
// (some environments like React Native may not implement it), which
|
|
1334
|
-
// caused the query string to be omitted on native. Checking the
|
|
1335
|
-
// serialized string is reliable everywhere.
|
|
1336
|
-
const qs = params.toString();
|
|
1337
|
-
return `${base}/api/assets/${encodeURIComponent(fileId)}/stream${qs ? `?${qs}` : ''}`;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Get file stream URL (direct Oxy Cloud/CDN URL, no token)
|
|
1342
|
-
*/
|
|
1343
|
-
getFileStreamUrl(fileId: string): string {
|
|
1344
|
-
return `${this.getCloudURL()}/files/${fileId}/stream`;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
// ...existing code...
|
|
1348
|
-
|
|
1349
|
-
/**
|
|
1350
|
-
* List user files
|
|
1351
|
-
*/
|
|
1352
|
-
async listUserFiles(limit?: number, offset?: number): Promise<{ files: any[]; total: number; hasMore: boolean }> {
|
|
1353
|
-
try {
|
|
1354
|
-
const paramsObj: any = {};
|
|
1355
|
-
if (limit) paramsObj.limit = limit;
|
|
1356
|
-
if (offset) paramsObj.offset = offset;
|
|
1357
|
-
return await this.makeRequest('GET', '/api/assets', paramsObj, {
|
|
1358
|
-
cache: false, // Don't cache file lists - always get fresh data
|
|
1359
|
-
});
|
|
1360
|
-
} catch (error) {
|
|
1361
|
-
throw this.handleError(error);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// (removed legacy downloadFileContent; use getFileContentAsBlob/Text which resolve CAS URL first)
|
|
1366
|
-
|
|
1367
|
-
/**
|
|
1368
|
-
* Get file content as text
|
|
1369
|
-
*/
|
|
1370
|
-
async getFileContentAsText(fileId: string, variant?: string): Promise<string> {
|
|
1371
|
-
try {
|
|
1372
|
-
const params: any = variant ? { variant } : undefined;
|
|
1373
|
-
const urlRes = await this.makeRequest<{ url: string }>('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
|
|
1374
|
-
cache: true,
|
|
1375
|
-
cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
|
|
1376
|
-
});
|
|
1377
|
-
const downloadUrl = urlRes?.url;
|
|
1378
|
-
const response = await fetch(downloadUrl);
|
|
1379
|
-
return await response.text();
|
|
1380
|
-
} catch (error) {
|
|
1381
|
-
throw this.handleError(error);
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
/**
|
|
1386
|
-
* Get file content as blob
|
|
1387
|
-
*/
|
|
1388
|
-
async getFileContentAsBlob(fileId: string, variant?: string): Promise<Blob> {
|
|
1389
|
-
try {
|
|
1390
|
-
const params: any = variant ? { variant } : undefined;
|
|
1391
|
-
const urlRes = await this.makeRequest<{ url: string }>('GET', `/api/assets/${encodeURIComponent(fileId)}/url`, params, {
|
|
1392
|
-
cache: true,
|
|
1393
|
-
cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
|
|
1394
|
-
});
|
|
1395
|
-
const downloadUrl = urlRes?.url;
|
|
1396
|
-
const response = await fetch(downloadUrl);
|
|
1397
|
-
return await response.blob();
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
throw this.handleError(error);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
/**
|
|
1404
|
-
* Upload raw file data
|
|
1405
|
-
*/
|
|
1406
|
-
async uploadRawFile(file: File | Blob, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>): Promise<any> {
|
|
1407
|
-
// Switch to Central Asset Service upload flow
|
|
1408
|
-
return this.assetUpload(file as File, visibility, metadata);
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// ============================================================================
|
|
1412
|
-
// CENTRAL ASSET SERVICE METHODS
|
|
1413
|
-
// ============================================================================
|
|
1414
|
-
|
|
1415
|
-
/**
|
|
1416
|
-
* Calculate SHA256 hash of file content
|
|
1417
|
-
*/
|
|
1418
|
-
async calculateSHA256(file: File | Blob): Promise<string> {
|
|
1419
|
-
const buffer = await file.arrayBuffer();
|
|
1420
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
|
1421
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1422
|
-
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
/**
|
|
1426
|
-
* Initialize asset upload - returns pre-signed URL and file ID
|
|
1427
|
-
*/
|
|
1428
|
-
async assetInit(sha256: string, size: number, mime: string): Promise<AssetInitResponse> {
|
|
1429
|
-
try {
|
|
1430
|
-
return await this.makeRequest<AssetInitResponse>('POST', '/api/assets/init', {
|
|
1431
|
-
sha256,
|
|
1432
|
-
size,
|
|
1433
|
-
mime
|
|
1434
|
-
}, { cache: false });
|
|
1435
|
-
} catch (error) {
|
|
1436
|
-
throw this.handleError(error);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
/**
|
|
1441
|
-
* Complete asset upload - commit metadata and trigger variant generation
|
|
1442
|
-
*/
|
|
1443
|
-
async assetComplete(fileId: string, originalName: string, size: number, mime: string, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>): Promise<any> {
|
|
1444
|
-
try {
|
|
1445
|
-
return await this.makeRequest('POST', '/api/assets/complete', {
|
|
1446
|
-
fileId,
|
|
1447
|
-
originalName,
|
|
1448
|
-
size,
|
|
1449
|
-
mime,
|
|
1450
|
-
visibility,
|
|
1451
|
-
metadata
|
|
1452
|
-
}, { cache: false });
|
|
1453
|
-
} catch (error) {
|
|
1454
|
-
throw this.handleError(error);
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
/**
|
|
1459
|
-
* Upload file using Central Asset Service
|
|
1460
|
-
*/
|
|
1461
|
-
async assetUpload(file: File, visibility?: 'private' | 'public' | 'unlisted', metadata?: Record<string, any>, onProgress?: (progress: number) => void): Promise<any> {
|
|
1462
|
-
try {
|
|
1463
|
-
// Calculate SHA256
|
|
1464
|
-
const sha256 = await this.calculateSHA256(file);
|
|
1465
|
-
|
|
1466
|
-
// Initialize upload
|
|
1467
|
-
const initResponse = await this.assetInit(sha256, file.size, file.type);
|
|
1468
|
-
|
|
1469
|
-
// Try presigned URL first
|
|
1470
|
-
try {
|
|
1471
|
-
await this.uploadToPresignedUrl(initResponse.uploadUrl, file, onProgress);
|
|
1472
|
-
} catch (e) {
|
|
1473
|
-
// Fallback: direct upload via API to avoid CORS issues
|
|
1474
|
-
const fd = new FormData();
|
|
1475
|
-
fd.append('file', file);
|
|
1476
|
-
// Use httpClient directly for FormData uploads (bypasses RequestManager for special handling)
|
|
1477
|
-
await this.httpClient.request({
|
|
1478
|
-
method: 'POST',
|
|
1479
|
-
url: `/api/assets/${encodeURIComponent(initResponse.fileId)}/upload-direct`,
|
|
1480
|
-
data: fd,
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Complete upload
|
|
1485
|
-
return await this.assetComplete(
|
|
1486
|
-
initResponse.fileId,
|
|
1487
|
-
file.name,
|
|
1488
|
-
file.size,
|
|
1489
|
-
file.type,
|
|
1490
|
-
visibility,
|
|
1491
|
-
metadata
|
|
1492
|
-
);
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
throw this.handleError(error);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
/**
|
|
1499
|
-
* Upload file to pre-signed URL
|
|
1500
|
-
*/
|
|
1501
|
-
private async uploadToPresignedUrl(url: string, file: File, onProgress?: (progress: number) => void): Promise<void> {
|
|
1502
|
-
return new Promise((resolve, reject) => {
|
|
1503
|
-
const xhr = new XMLHttpRequest();
|
|
1504
|
-
|
|
1505
|
-
xhr.upload.addEventListener('progress', (event) => {
|
|
1506
|
-
if (event.lengthComputable && onProgress) {
|
|
1507
|
-
const progress = (event.loaded / event.total) * 100;
|
|
1508
|
-
onProgress(progress);
|
|
1509
|
-
}
|
|
1510
|
-
});
|
|
1511
|
-
|
|
1512
|
-
xhr.addEventListener('load', () => {
|
|
1513
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
1514
|
-
resolve();
|
|
1515
|
-
} else {
|
|
1516
|
-
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
1517
|
-
}
|
|
1518
|
-
});
|
|
1519
|
-
|
|
1520
|
-
xhr.addEventListener('error', () => {
|
|
1521
|
-
reject(new Error('Upload failed'));
|
|
1522
|
-
});
|
|
1523
|
-
|
|
1524
|
-
xhr.open('PUT', url);
|
|
1525
|
-
xhr.setRequestHeader('Content-Type', file.type);
|
|
1526
|
-
xhr.send(file);
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* Link asset to an entity
|
|
1532
|
-
*/
|
|
1533
|
-
async assetLink(fileId: string, app: string, entityType: string, entityId: string, visibility?: 'private' | 'public' | 'unlisted', webhookUrl?: string): Promise<any> {
|
|
1534
|
-
try {
|
|
1535
|
-
const body: any = { app, entityType, entityId };
|
|
1536
|
-
if (visibility) body.visibility = visibility;
|
|
1537
|
-
if (webhookUrl) body.webhookUrl = webhookUrl;
|
|
1538
|
-
return await this.makeRequest('POST', `/api/assets/${fileId}/links`, body, { cache: false });
|
|
1539
|
-
} catch (error) {
|
|
1540
|
-
throw this.handleError(error);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
/**
|
|
1545
|
-
* Unlink asset from an entity
|
|
1546
|
-
*/
|
|
1547
|
-
async assetUnlink(fileId: string, app: string, entityType: string, entityId: string): Promise<any> {
|
|
1548
|
-
try {
|
|
1549
|
-
return await this.makeRequest('DELETE', `/api/assets/${fileId}/links`, {
|
|
1550
|
-
app,
|
|
1551
|
-
entityType,
|
|
1552
|
-
entityId
|
|
1553
|
-
}, { cache: false });
|
|
1554
|
-
} catch (error) {
|
|
1555
|
-
throw this.handleError(error);
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
/**
|
|
1560
|
-
* Get asset metadata
|
|
1561
|
-
*/
|
|
1562
|
-
async assetGet(fileId: string): Promise<any> {
|
|
1563
|
-
try {
|
|
1564
|
-
return await this.makeRequest('GET', `/api/assets/${fileId}`, undefined, {
|
|
1565
|
-
cache: true,
|
|
1566
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
1567
|
-
});
|
|
1568
|
-
} catch (error) {
|
|
1569
|
-
throw this.handleError(error);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
/**
|
|
1574
|
-
* Get asset URL (CDN or signed URL)
|
|
1575
|
-
*/
|
|
1576
|
-
async assetGetUrl(fileId: string, variant?: string, expiresIn?: number): Promise<AssetUrlResponse> {
|
|
1577
|
-
try {
|
|
1578
|
-
const params: any = {};
|
|
1579
|
-
if (variant) params.variant = variant;
|
|
1580
|
-
if (expiresIn) params.expiresIn = expiresIn;
|
|
1581
|
-
|
|
1582
|
-
return await this.makeRequest<AssetUrlResponse>('GET', `/api/assets/${fileId}/url`, params, {
|
|
1583
|
-
cache: true,
|
|
1584
|
-
cacheTTL: 10 * 60 * 1000, // 10 minutes cache for URLs
|
|
1585
|
-
});
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
throw this.handleError(error);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
/**
|
|
1592
|
-
* Restore asset from trash
|
|
1593
|
-
*/
|
|
1594
|
-
async assetRestore(fileId: string): Promise<any> {
|
|
1595
|
-
try {
|
|
1596
|
-
return await this.makeRequest('POST', `/api/assets/${fileId}/restore`, undefined, { cache: false });
|
|
1597
|
-
} catch (error) {
|
|
1598
|
-
throw this.handleError(error);
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* Delete asset with optional force
|
|
1604
|
-
*/
|
|
1605
|
-
async assetDelete(fileId: string, force: boolean = false): Promise<any> {
|
|
1606
|
-
try {
|
|
1607
|
-
const params: any = force ? { force: 'true' } : undefined;
|
|
1608
|
-
return await this.makeRequest('DELETE', `/api/assets/${fileId}`, params, { cache: false });
|
|
1609
|
-
} catch (error) {
|
|
1610
|
-
throw this.handleError(error);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
/**
|
|
1615
|
-
* Get list of available variants for an asset
|
|
1616
|
-
*/
|
|
1617
|
-
async assetGetVariants(fileId: string): Promise<AssetVariant[]> {
|
|
1618
|
-
try {
|
|
1619
|
-
const assetData = await this.assetGet(fileId);
|
|
1620
|
-
return assetData.file?.variants || [];
|
|
1621
|
-
} catch (error) {
|
|
1622
|
-
throw this.handleError(error);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
/**
|
|
1627
|
-
* Update asset visibility
|
|
1628
|
-
* @param fileId - The file ID
|
|
1629
|
-
* @param visibility - New visibility level ('private', 'public', or 'unlisted')
|
|
1630
|
-
* @returns Updated asset information
|
|
1631
|
-
*/
|
|
1632
|
-
async assetUpdateVisibility(fileId: string, visibility: 'private' | 'public' | 'unlisted'): Promise<any> {
|
|
1633
|
-
try {
|
|
1634
|
-
return await this.makeRequest('PATCH', `/api/assets/${fileId}/visibility`, {
|
|
1635
|
-
visibility
|
|
1636
|
-
}, { cache: false });
|
|
1637
|
-
} catch (error) {
|
|
1638
|
-
throw this.handleError(error);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* Helper: Upload and link avatar with automatic public visibility
|
|
1644
|
-
* @param file - The avatar file
|
|
1645
|
-
* @param userId - User ID to link to
|
|
1646
|
-
* @param app - App name (defaults to 'profiles')
|
|
1647
|
-
* @returns The uploaded and linked asset
|
|
1648
|
-
*/
|
|
1649
|
-
async uploadAvatar(file: File, userId: string, app: string = 'profiles'): Promise<any> {
|
|
1650
|
-
try {
|
|
1651
|
-
// Upload as public
|
|
1652
|
-
const asset = await this.assetUpload(file, 'public');
|
|
1653
|
-
|
|
1654
|
-
// Link to user profile as avatar
|
|
1655
|
-
await this.assetLink(asset.file.id, app, 'avatar', userId, 'public');
|
|
1656
|
-
|
|
1657
|
-
return asset;
|
|
1658
|
-
} catch (error) {
|
|
1659
|
-
throw this.handleError(error);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
/**
|
|
1664
|
-
* Helper: Upload and link profile banner with automatic public visibility
|
|
1665
|
-
* @param file - The banner file
|
|
1666
|
-
* @param userId - User ID to link to
|
|
1667
|
-
* @param app - App name (defaults to 'profiles')
|
|
1668
|
-
* @returns The uploaded and linked asset
|
|
1669
|
-
*/
|
|
1670
|
-
async uploadProfileBanner(file: File, userId: string, app: string = 'profiles'): Promise<any> {
|
|
1671
|
-
try {
|
|
1672
|
-
// Upload as public
|
|
1673
|
-
const asset = await this.assetUpload(file, 'public');
|
|
1674
|
-
|
|
1675
|
-
// Link to user profile as banner
|
|
1676
|
-
await this.assetLink(asset.file.id, app, 'profile-banner', userId, 'public');
|
|
1677
|
-
|
|
1678
|
-
return asset;
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
throw this.handleError(error);
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// ============================================================================
|
|
1685
|
-
// DEVELOPER API METHODS
|
|
1686
|
-
// ============================================================================
|
|
1687
|
-
|
|
1688
|
-
/**
|
|
1689
|
-
* Get developer apps for the current user
|
|
1690
|
-
*/
|
|
1691
|
-
async getDeveloperApps(): Promise<any[]> {
|
|
1692
|
-
try {
|
|
1693
|
-
const res = await this.makeRequest<{ apps?: any[] }>('GET', '/api/developer/apps', undefined, {
|
|
1694
|
-
cache: true,
|
|
1695
|
-
cacheTTL: 2 * 60 * 1000, // 2 minutes cache
|
|
1696
|
-
});
|
|
1697
|
-
return res.apps || [];
|
|
1698
|
-
} catch (error) {
|
|
1699
|
-
throw this.handleError(error);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
/**
|
|
1704
|
-
* Create a new developer app
|
|
1705
|
-
*/
|
|
1706
|
-
async createDeveloperApp(data: {
|
|
1707
|
-
name: string;
|
|
1708
|
-
description?: string;
|
|
1709
|
-
webhookUrl: string;
|
|
1710
|
-
devWebhookUrl?: string;
|
|
1711
|
-
scopes?: string[];
|
|
1712
|
-
}): Promise<any> {
|
|
1713
|
-
try {
|
|
1714
|
-
const res = await this.makeRequest<{ app: any }>('POST', '/api/developer/apps', data, { cache: false });
|
|
1715
|
-
return res.app;
|
|
1716
|
-
} catch (error) {
|
|
1717
|
-
throw this.handleError(error);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/**
|
|
1722
|
-
* Get a specific developer app
|
|
1723
|
-
*/
|
|
1724
|
-
async getDeveloperApp(appId: string): Promise<any> {
|
|
1725
|
-
try {
|
|
1726
|
-
const res = await this.makeRequest<{ app: any }>('GET', `/api/developer/apps/${appId}`, undefined, {
|
|
1727
|
-
cache: true,
|
|
1728
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
1729
|
-
});
|
|
1730
|
-
return res.app;
|
|
1731
|
-
} catch (error) {
|
|
1732
|
-
throw this.handleError(error);
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
/**
|
|
1737
|
-
* Update a developer app
|
|
1738
|
-
*/
|
|
1739
|
-
async updateDeveloperApp(appId: string, data: {
|
|
1740
|
-
name?: string;
|
|
1741
|
-
description?: string;
|
|
1742
|
-
webhookUrl?: string;
|
|
1743
|
-
devWebhookUrl?: string;
|
|
1744
|
-
scopes?: string[];
|
|
1745
|
-
}): Promise<any> {
|
|
1746
|
-
try {
|
|
1747
|
-
const res = await this.makeRequest<{ app: any }>('PATCH', `/api/developer/apps/${appId}`, data, { cache: false });
|
|
1748
|
-
return res.app;
|
|
1749
|
-
} catch (error) {
|
|
1750
|
-
throw this.handleError(error);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
/**
|
|
1755
|
-
* Regenerate API secret for a developer app
|
|
1756
|
-
*/
|
|
1757
|
-
async regenerateDeveloperAppSecret(appId: string): Promise<any> {
|
|
1758
|
-
try {
|
|
1759
|
-
return await this.makeRequest('POST', `/api/developer/apps/${appId}/regenerate-secret`, undefined, { cache: false });
|
|
1760
|
-
} catch (error) {
|
|
1761
|
-
throw this.handleError(error);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
/**
|
|
1766
|
-
* Delete a developer app
|
|
1767
|
-
*/
|
|
1768
|
-
async deleteDeveloperApp(appId: string): Promise<any> {
|
|
1769
|
-
try {
|
|
1770
|
-
return await this.makeRequest('DELETE', `/api/developer/apps/${appId}`, undefined, { cache: false });
|
|
1771
|
-
} catch (error) {
|
|
1772
|
-
throw this.handleError(error);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
// ============================================================================
|
|
1777
|
-
// LOCATION METHODS
|
|
1778
|
-
// ============================================================================
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* Update user location
|
|
1782
|
-
*/
|
|
1783
|
-
async updateLocation(latitude: number, longitude: number): Promise<any> {
|
|
1784
|
-
try {
|
|
1785
|
-
return await this.makeRequest('POST', '/api/location', {
|
|
1786
|
-
latitude,
|
|
1787
|
-
longitude
|
|
1788
|
-
}, { cache: false });
|
|
1789
|
-
} catch (error) {
|
|
1790
|
-
throw this.handleError(error);
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
* Get nearby users
|
|
1796
|
-
*/
|
|
1797
|
-
async getNearbyUsers(radius?: number): Promise<any[]> {
|
|
1798
|
-
try {
|
|
1799
|
-
const params: any = radius ? { radius } : undefined;
|
|
1800
|
-
return await this.makeRequest('GET', '/api/location/nearby', params, {
|
|
1801
|
-
cache: false, // Don't cache location data - always get fresh data
|
|
1802
|
-
});
|
|
1803
|
-
} catch (error) {
|
|
1804
|
-
throw this.handleError(error);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
// ============================================================================
|
|
1809
|
-
// ANALYTICS METHODS
|
|
1810
|
-
// ============================================================================
|
|
1811
|
-
|
|
1812
|
-
/**
|
|
1813
|
-
* Track event
|
|
1814
|
-
*/
|
|
1815
|
-
async trackEvent(eventName: string, properties?: Record<string, any>): Promise<void> {
|
|
1816
|
-
try {
|
|
1817
|
-
await this.makeRequest('POST', '/api/analytics/events', {
|
|
1818
|
-
event: eventName,
|
|
1819
|
-
properties
|
|
1820
|
-
}, { cache: false, retry: false }); // Don't retry analytics events
|
|
1821
|
-
} catch (error) {
|
|
1822
|
-
throw this.handleError(error);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
/**
|
|
1827
|
-
* Get analytics data
|
|
1828
|
-
*/
|
|
1829
|
-
async getAnalytics(startDate?: string, endDate?: string): Promise<any> {
|
|
1830
|
-
try {
|
|
1831
|
-
const params: any = {};
|
|
1832
|
-
if (startDate) params.startDate = startDate;
|
|
1833
|
-
if (endDate) params.endDate = endDate;
|
|
1834
|
-
|
|
1835
|
-
return await this.makeRequest('GET', '/api/analytics', params, {
|
|
1836
|
-
cache: true,
|
|
1837
|
-
cacheTTL: 5 * 60 * 1000, // 5 minutes cache
|
|
1838
|
-
});
|
|
1839
|
-
} catch (error) {
|
|
1840
|
-
throw this.handleError(error);
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
// ============================================================================
|
|
1845
|
-
// DEVICE METHODS
|
|
1846
|
-
// ============================================================================
|
|
1847
|
-
|
|
1848
|
-
/**
|
|
1849
|
-
* Register device
|
|
1850
|
-
*/
|
|
1851
|
-
async registerDevice(deviceData: any): Promise<any> {
|
|
1852
|
-
try {
|
|
1853
|
-
return await this.makeRequest('POST', '/api/devices', deviceData, { cache: false });
|
|
1854
|
-
} catch (error) {
|
|
1855
|
-
throw this.handleError(error);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
/**
|
|
1860
|
-
* Get user devices
|
|
1861
|
-
*/
|
|
1862
|
-
async getUserDevices(): Promise<any[]> {
|
|
1863
|
-
try {
|
|
1864
|
-
return await this.makeRequest('GET', '/api/devices', undefined, {
|
|
1865
|
-
cache: false, // Don't cache device list - always get fresh data
|
|
1866
|
-
});
|
|
1867
|
-
} catch (error) {
|
|
1868
|
-
throw this.handleError(error);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
/**
|
|
1873
|
-
* Remove device
|
|
1874
|
-
*/
|
|
1875
|
-
async removeDevice(deviceId: string): Promise<void> {
|
|
1876
|
-
try {
|
|
1877
|
-
await this.makeRequest('DELETE', `/api/devices/${deviceId}`, undefined, { cache: false });
|
|
1878
|
-
} catch (error) {
|
|
1879
|
-
throw this.handleError(error);
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
/**
|
|
1884
|
-
* Get device sessions
|
|
1885
|
-
* Note: Not cached by default to ensure fresh data, but can be cached via makeRequest if needed
|
|
1886
|
-
*/
|
|
1887
|
-
async getDeviceSessions(sessionId: string): Promise<any[]> {
|
|
1888
|
-
try {
|
|
1889
|
-
// Use makeRequest for consistent error handling and optional caching
|
|
1890
|
-
// Cache disabled by default to ensure fresh session data
|
|
1891
|
-
return await this.makeRequest<any[]>('GET', `/api/session/device/sessions/${sessionId}`, undefined, {
|
|
1892
|
-
cache: false, // Don't cache sessions - always get fresh data
|
|
1893
|
-
deduplicate: true, // Deduplicate concurrent requests for same sessionId
|
|
1894
|
-
});
|
|
1895
|
-
} catch (error) {
|
|
1896
|
-
throw this.handleError(error);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
/**
|
|
1901
|
-
* Logout all device sessions
|
|
1902
|
-
*/
|
|
1903
|
-
async logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<any> {
|
|
1904
|
-
try {
|
|
1905
|
-
const params = new URLSearchParams();
|
|
1906
|
-
if (deviceId) params.append('deviceId', deviceId);
|
|
1907
|
-
if (excludeCurrent) params.append('excludeCurrent', 'true');
|
|
1908
|
-
|
|
1909
|
-
const urlParams: any = {};
|
|
1910
|
-
params.forEach((value, key) => {
|
|
1911
|
-
urlParams[key] = value;
|
|
1912
|
-
});
|
|
1913
|
-
return await this.makeRequest('POST', `/api/session/device/logout-all/${sessionId}`, urlParams, { cache: false });
|
|
1914
|
-
} catch (error) {
|
|
1915
|
-
throw this.handleError(error);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
/**
|
|
1920
|
-
* Update device name
|
|
1921
|
-
*/
|
|
1922
|
-
async updateDeviceName(sessionId: string, deviceName: string): Promise<any> {
|
|
1923
|
-
try {
|
|
1924
|
-
return await this.makeRequest('PUT', `/api/session/device/name/${sessionId}`, { deviceName }, { cache: false });
|
|
1925
|
-
} catch (error) {
|
|
1926
|
-
throw this.handleError(error);
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
// ============================================================================
|
|
1931
|
-
// UTILITY METHODS
|
|
1932
|
-
// ============================================================================
|
|
1933
|
-
|
|
1934
|
-
/**
|
|
1935
|
-
* Fetch link metadata
|
|
1936
|
-
*/
|
|
1937
|
-
async fetchLinkMetadata(url: string): Promise<{
|
|
1938
|
-
url: string;
|
|
1939
|
-
title: string;
|
|
1940
|
-
description: string;
|
|
1941
|
-
image?: string;
|
|
1942
|
-
}> {
|
|
1943
|
-
try {
|
|
1944
|
-
return await this.makeRequest<{
|
|
1945
|
-
url: string;
|
|
1946
|
-
title: string;
|
|
1947
|
-
description: string;
|
|
1948
|
-
image?: string;
|
|
1949
|
-
}>('GET', '/api/link-metadata', { url }, {
|
|
1950
|
-
cache: true,
|
|
1951
|
-
cacheTTL: 30 * 60 * 1000, // 30 minutes cache for link metadata
|
|
1952
|
-
});
|
|
1953
|
-
} catch (error) {
|
|
1954
|
-
throw this.handleError(error);
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
/**
|
|
1959
|
-
* Simple Express.js authentication middleware
|
|
1960
|
-
*
|
|
1961
|
-
* Built-in authentication middleware that validates JWT tokens and adds user data to requests.
|
|
1962
|
-
*
|
|
1963
|
-
* @example
|
|
1964
|
-
* ```typescript
|
|
1965
|
-
* // Basic usage - just add it to your routes
|
|
1966
|
-
* app.use('/api/protected', oxyServices.auth());
|
|
1967
|
-
*
|
|
1968
|
-
* // With debug logging
|
|
1969
|
-
* app.use('/api/protected', oxyServices.auth({ debug: true }));
|
|
1970
|
-
*
|
|
1971
|
-
* // With custom error handling
|
|
1972
|
-
* app.use('/api/protected', oxyServices.auth({
|
|
1973
|
-
* onError: (error) => console.error('Auth failed:', error)
|
|
1974
|
-
* }));
|
|
1975
|
-
*
|
|
1976
|
-
* // Load full user data
|
|
1977
|
-
* app.use('/api/protected', oxyServices.auth({ loadUser: true }));
|
|
1978
|
-
* ```
|
|
1979
|
-
*
|
|
1980
|
-
* @param options Optional configuration
|
|
1981
|
-
* @param options.debug Enable debug logging (default: false)
|
|
1982
|
-
* @param options.onError Custom error handler
|
|
1983
|
-
* @param options.loadUser Load full user data (default: false for performance)
|
|
1984
|
-
* @param options.session Use session-based validation (default: false)
|
|
1985
|
-
* @returns Express middleware function
|
|
1986
|
-
*/
|
|
1987
|
-
auth(options: {
|
|
1988
|
-
debug?: boolean;
|
|
1989
|
-
onError?: (error: ApiError) => any;
|
|
1990
|
-
loadUser?: boolean;
|
|
1991
|
-
session?: boolean;
|
|
1992
|
-
} = {}) {
|
|
1993
|
-
const { debug = false, onError, loadUser = false, session = false } = options;
|
|
1994
|
-
|
|
1995
|
-
// Return a synchronous middleware function
|
|
1996
|
-
return (req: any, res: any, next: any) => {
|
|
1997
|
-
try {
|
|
1998
|
-
// Extract token from Authorization header
|
|
1999
|
-
const authHeader = req.headers['authorization'];
|
|
2000
|
-
const token = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
|
|
2001
|
-
|
|
2002
|
-
if (debug) {
|
|
2003
|
-
console.log(`🔐 Auth: Processing ${req.method} ${req.path}`);
|
|
2004
|
-
console.log(`🔐 Auth: Token present: ${!!token}`);
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
if (!token) {
|
|
2008
|
-
const error = {
|
|
2009
|
-
message: 'Access token required',
|
|
2010
|
-
code: 'MISSING_TOKEN',
|
|
2011
|
-
status: 401
|
|
2012
|
-
};
|
|
2013
|
-
|
|
2014
|
-
if (debug) console.log(`❌ Auth: Missing token`);
|
|
2015
|
-
|
|
2016
|
-
if (onError) return onError(error);
|
|
2017
|
-
return res.status(401).json(error);
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
// Decode and validate token
|
|
2021
|
-
let decoded: JwtPayload;
|
|
2022
|
-
try {
|
|
2023
|
-
decoded = jwtDecode<JwtPayload>(token);
|
|
2024
|
-
|
|
2025
|
-
if (debug) {
|
|
2026
|
-
console.log(`🔐 Auth: Token decoded, User ID: ${decoded.userId || decoded.id}`);
|
|
2027
|
-
}
|
|
2028
|
-
} catch (decodeError) {
|
|
2029
|
-
const error = {
|
|
2030
|
-
message: 'Invalid token format',
|
|
2031
|
-
code: 'INVALID_TOKEN_FORMAT',
|
|
2032
|
-
status: 403
|
|
2033
|
-
};
|
|
2034
|
-
|
|
2035
|
-
if (debug) console.log(`❌ Auth: Token decode failed`);
|
|
2036
|
-
|
|
2037
|
-
if (onError) return onError(error);
|
|
2038
|
-
return res.status(403).json(error);
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
const userId = decoded.userId || decoded.id;
|
|
2042
|
-
if (!userId) {
|
|
2043
|
-
const error = {
|
|
2044
|
-
message: 'Token missing user ID',
|
|
2045
|
-
code: 'INVALID_TOKEN_PAYLOAD',
|
|
2046
|
-
status: 403
|
|
2047
|
-
};
|
|
2048
|
-
|
|
2049
|
-
if (debug) console.log(`❌ Auth: Token missing user ID`);
|
|
2050
|
-
|
|
2051
|
-
if (onError) return onError(error);
|
|
2052
|
-
return res.status(403).json(error);
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
// Check token expiration
|
|
2056
|
-
if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
|
|
2057
|
-
const error = {
|
|
2058
|
-
message: 'Token expired',
|
|
2059
|
-
code: 'TOKEN_EXPIRED',
|
|
2060
|
-
status: 403
|
|
2061
|
-
};
|
|
2062
|
-
|
|
2063
|
-
if (debug) console.log(`❌ Auth: Token expired`);
|
|
2064
|
-
|
|
2065
|
-
if (onError) return onError(error);
|
|
2066
|
-
return res.status(403).json(error);
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
// For now, skip session validation to keep it simple
|
|
2070
|
-
// Session validation can be added later if needed
|
|
2071
|
-
|
|
2072
|
-
// Set request properties immediately
|
|
2073
|
-
req.userId = userId;
|
|
2074
|
-
req.accessToken = token;
|
|
2075
|
-
req.user = { id: userId } as User;
|
|
2076
|
-
|
|
2077
|
-
if (debug) {
|
|
2078
|
-
console.log(`✅ Auth: Authentication successful for user ${userId}`);
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
next();
|
|
2082
|
-
} catch (error) {
|
|
2083
|
-
const apiError = this.handleError(error) as any;
|
|
2084
|
-
|
|
2085
|
-
if (debug) {
|
|
2086
|
-
console.log(`❌ Auth: Unexpected error:`, apiError);
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
if (onError) return onError(apiError);
|
|
2090
|
-
return res.status((apiError && apiError.status) || 500).json(apiError);
|
|
2091
|
-
}
|
|
2092
|
-
};
|
|
106
|
+
super(config);
|
|
2093
107
|
}
|
|
2094
108
|
}
|
|
2095
109
|
|
|
110
|
+
// Re-export error classes for convenience
|
|
111
|
+
export { OxyAuthenticationError, OxyAuthenticationTimeoutError };
|
|
112
|
+
|
|
2096
113
|
/**
|
|
2097
114
|
* Export the default Oxy Cloud URL (for backward compatibility)
|
|
2098
115
|
*/
|