@oxyhq/core 1.11.10 → 1.11.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +26 -18
- package/dist/cjs/OxyServices.base.js +21 -0
- package/dist/cjs/crypto/signatureService.js +11 -11
- package/dist/cjs/mixins/OxyServices.managedAccounts.js +117 -0
- package/dist/cjs/mixins/OxyServices.user.js +11 -0
- package/dist/cjs/mixins/OxyServices.utility.js +81 -2
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/cjs/utils/asyncUtils.js +34 -5
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +26 -18
- package/dist/esm/OxyServices.base.js +21 -0
- package/dist/esm/crypto/keyManager.js +3 -3
- package/dist/esm/crypto/polyfill.js +1 -1
- package/dist/esm/crypto/signatureService.js +12 -12
- package/dist/esm/mixins/OxyServices.language.js +1 -1
- package/dist/esm/mixins/OxyServices.managedAccounts.js +114 -0
- package/dist/esm/mixins/OxyServices.user.js +11 -0
- package/dist/esm/mixins/OxyServices.utility.js +81 -2
- package/dist/esm/mixins/index.js +2 -0
- package/dist/esm/utils/asyncUtils.js +34 -5
- package/dist/esm/utils/deviceManager.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -0
- package/dist/types/OxyServices.base.d.ts +17 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.analytics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +2 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +2 -0
- package/dist/types/mixins/OxyServices.developer.d.ts +2 -0
- package/dist/types/mixins/OxyServices.devices.d.ts +2 -0
- package/dist/types/mixins/OxyServices.features.d.ts +5 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +2 -0
- package/dist/types/mixins/OxyServices.karma.d.ts +2 -0
- package/dist/types/mixins/OxyServices.language.d.ts +2 -0
- package/dist/types/mixins/OxyServices.location.d.ts +2 -0
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +125 -0
- package/dist/types/mixins/OxyServices.payment.d.ts +2 -0
- package/dist/types/mixins/OxyServices.popup.d.ts +2 -0
- package/dist/types/mixins/OxyServices.privacy.d.ts +2 -0
- package/dist/types/mixins/OxyServices.redirect.d.ts +2 -0
- package/dist/types/mixins/OxyServices.security.d.ts +2 -0
- package/dist/types/mixins/OxyServices.topics.d.ts +2 -0
- package/dist/types/mixins/OxyServices.user.d.ts +14 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +22 -0
- package/dist/types/models/interfaces.d.ts +2 -0
- package/dist/types/utils/asyncUtils.d.ts +6 -2
- package/package.json +1 -1
- package/src/HttpService.ts +30 -11
- package/src/OxyServices.base.ts +23 -0
- package/src/crypto/keyManager.ts +3 -3
- package/src/crypto/polyfill.ts +1 -1
- package/src/crypto/signatureService.ts +13 -12
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.language.ts +1 -1
- package/src/mixins/OxyServices.managedAccounts.ts +147 -0
- package/src/mixins/OxyServices.user.ts +18 -0
- package/src/mixins/OxyServices.utility.ts +103 -2
- package/src/mixins/index.ts +2 -0
- package/src/models/interfaces.ts +3 -0
- package/src/utils/__tests__/asyncUtils.test.ts +187 -0
- package/src/utils/asyncUtils.ts +39 -9
- package/src/utils/deviceManager.ts +1 -1
|
@@ -50,6 +50,7 @@ export declare class HttpService {
|
|
|
50
50
|
private tokenRefreshPromise;
|
|
51
51
|
private tokenRefreshCooldownUntil;
|
|
52
52
|
private _onTokenRefreshed;
|
|
53
|
+
private _actingAsUserId;
|
|
53
54
|
private requestMetrics;
|
|
54
55
|
constructor(config: OxyConfig);
|
|
55
56
|
/**
|
|
@@ -93,6 +94,8 @@ export declare class HttpService {
|
|
|
93
94
|
put<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'method' | 'url' | 'data'>): Promise<T>;
|
|
94
95
|
patch<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'method' | 'url' | 'data'>): Promise<T>;
|
|
95
96
|
delete<T = unknown>(url: string, config?: Omit<RequestConfig, 'method' | 'url'>): Promise<T>;
|
|
97
|
+
setActingAs(userId: string | null): void;
|
|
98
|
+
getActingAs(): string | null;
|
|
96
99
|
setTokens(accessToken: string, refreshToken?: string): void;
|
|
97
100
|
set onTokenRefreshed(callback: ((accessToken: string) => void) | null);
|
|
98
101
|
clearTokens(): void;
|
|
@@ -81,6 +81,23 @@ export declare class OxyServicesBase {
|
|
|
81
81
|
* Get the raw access token (for constructing anchor URLs when needed)
|
|
82
82
|
*/
|
|
83
83
|
getAccessToken(): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* Set the acting-as identity for managed accounts.
|
|
86
|
+
*
|
|
87
|
+
* When set, all subsequent API requests will include the `X-Acting-As` header,
|
|
88
|
+
* causing the server to attribute actions to the managed account. The
|
|
89
|
+
* authenticated user must be an authorized manager of the target account.
|
|
90
|
+
*
|
|
91
|
+
* Pass `null` to clear and revert to the authenticated user's own identity.
|
|
92
|
+
*
|
|
93
|
+
* @param userId - The managed account user ID, or null to clear
|
|
94
|
+
*/
|
|
95
|
+
setActingAs(userId: string | null): void;
|
|
96
|
+
/**
|
|
97
|
+
* Get the current acting-as identity (managed account user ID), or null
|
|
98
|
+
* if operating as the authenticated user's own identity.
|
|
99
|
+
*/
|
|
100
|
+
getActingAs(): string | null;
|
|
84
101
|
/**
|
|
85
102
|
* Wait for authentication to be ready
|
|
86
103
|
*
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export type { PopupAuthOptions } from './mixins/OxyServices.popup';
|
|
|
25
25
|
export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
|
|
26
26
|
export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
|
|
27
27
|
export type { ServiceApp } from './mixins/OxyServices.utility';
|
|
28
|
+
export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount } from './mixins/OxyServices.managedAccounts';
|
|
28
29
|
export { KeyManager, SignatureService, RecoveryPhraseService } from './crypto';
|
|
29
30
|
export type { KeyPair, SignedMessage, AuthChallenge, RecoveryPhraseResult } from './crypto';
|
|
30
31
|
export * from './models/interfaces';
|
|
@@ -50,6 +50,8 @@ export declare function OxyServicesAnalyticsMixin<T extends typeof OxyServicesBa
|
|
|
50
50
|
getCurrentUserId(): string | null;
|
|
51
51
|
hasValidToken(): boolean;
|
|
52
52
|
getAccessToken(): string | null;
|
|
53
|
+
setActingAs(userId: string | null): void;
|
|
54
|
+
getActingAs(): string | null;
|
|
53
55
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
54
56
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
55
57
|
maxRetries?: number;
|
|
@@ -128,6 +128,8 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
|
|
|
128
128
|
getCurrentUserId(): string | null;
|
|
129
129
|
hasValidToken(): boolean;
|
|
130
130
|
getAccessToken(): string | null;
|
|
131
|
+
setActingAs(userId: string | null): void;
|
|
132
|
+
getActingAs(): string | null;
|
|
131
133
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
132
134
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
133
135
|
maxRetries?: number;
|
|
@@ -206,6 +206,8 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
|
|
|
206
206
|
getCurrentUserId(): string | null;
|
|
207
207
|
hasValidToken(): boolean;
|
|
208
208
|
getAccessToken(): string | null;
|
|
209
|
+
setActingAs(userId: string | null): void;
|
|
210
|
+
getActingAs(): string | null;
|
|
209
211
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
210
212
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
211
213
|
maxRetries?: number;
|
|
@@ -83,6 +83,8 @@ export declare function OxyServicesDeveloperMixin<T extends typeof OxyServicesBa
|
|
|
83
83
|
getCurrentUserId(): string | null;
|
|
84
84
|
hasValidToken(): boolean;
|
|
85
85
|
getAccessToken(): string | null;
|
|
86
|
+
setActingAs(userId: string | null): void;
|
|
87
|
+
getActingAs(): string | null;
|
|
86
88
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
87
89
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
88
90
|
maxRetries?: number;
|
|
@@ -80,6 +80,8 @@ export declare function OxyServicesDevicesMixin<T extends typeof OxyServicesBase
|
|
|
80
80
|
getCurrentUserId(): string | null;
|
|
81
81
|
hasValidToken(): boolean;
|
|
82
82
|
getAccessToken(): string | null;
|
|
83
|
+
setActingAs(userId: string | null): void;
|
|
84
|
+
getActingAs(): string | null;
|
|
83
85
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
84
86
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
85
87
|
maxRetries?: number;
|
|
@@ -212,6 +212,8 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
212
212
|
getCurrentUserId(): string | null;
|
|
213
213
|
hasValidToken(): boolean;
|
|
214
214
|
getAccessToken(): string | null;
|
|
215
|
+
setActingAs(userId: string | null): void;
|
|
216
|
+
getActingAs(): string | null;
|
|
215
217
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
216
218
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
217
219
|
maxRetries?: number;
|
|
@@ -223,7 +225,9 @@ export declare function OxyServicesFeaturesMixin<T extends typeof OxyServicesBas
|
|
|
223
225
|
healthCheck(): Promise<{
|
|
224
226
|
status: string;
|
|
225
227
|
users?: number;
|
|
226
|
-
timestamp
|
|
228
|
+
timestamp? /**
|
|
229
|
+
* Get FAQs
|
|
230
|
+
*/: string;
|
|
227
231
|
[key: string]: any;
|
|
228
232
|
}>;
|
|
229
233
|
};
|
|
@@ -188,6 +188,8 @@ export declare function OxyServicesFedCMMixin<T extends typeof OxyServicesBase>(
|
|
|
188
188
|
getCurrentUserId(): string | null;
|
|
189
189
|
hasValidToken(): boolean;
|
|
190
190
|
getAccessToken(): string | null;
|
|
191
|
+
setActingAs(userId: string | null): void;
|
|
192
|
+
getActingAs(): string | null;
|
|
191
193
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
192
194
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
193
195
|
maxRetries?: number;
|
|
@@ -69,6 +69,8 @@ export declare function OxyServicesKarmaMixin<T extends typeof OxyServicesBase>(
|
|
|
69
69
|
getCurrentUserId(): string | null;
|
|
70
70
|
hasValidToken(): boolean;
|
|
71
71
|
getAccessToken(): string | null;
|
|
72
|
+
setActingAs(userId: string | null): void;
|
|
73
|
+
getActingAs(): string | null;
|
|
72
74
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
73
75
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
74
76
|
maxRetries?: number;
|
|
@@ -65,6 +65,8 @@ export declare function OxyServicesLanguageMixin<T extends typeof OxyServicesBas
|
|
|
65
65
|
getCurrentUserId(): string | null;
|
|
66
66
|
hasValidToken(): boolean;
|
|
67
67
|
getAccessToken(): string | null;
|
|
68
|
+
setActingAs(userId: string | null): void;
|
|
69
|
+
getActingAs(): string | null;
|
|
68
70
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
69
71
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
70
72
|
maxRetries?: number;
|
|
@@ -48,6 +48,8 @@ export declare function OxyServicesLocationMixin<T extends typeof OxyServicesBas
|
|
|
48
48
|
getCurrentUserId(): string | null;
|
|
49
49
|
hasValidToken(): boolean;
|
|
50
50
|
getAccessToken(): string | null;
|
|
51
|
+
setActingAs(userId: string | null): void;
|
|
52
|
+
getActingAs(): string | null;
|
|
51
53
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
52
54
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
53
55
|
maxRetries?: number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed Accounts Methods Mixin
|
|
3
|
+
*
|
|
4
|
+
* Provides SDK methods for creating and managing sub-accounts (managed identities).
|
|
5
|
+
* Managed accounts are full User documents without passwords, accessible only
|
|
6
|
+
* by their owners/managers via the X-Acting-As header mechanism.
|
|
7
|
+
*/
|
|
8
|
+
import type { User } from '../models/interfaces';
|
|
9
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
10
|
+
export interface CreateManagedAccountInput {
|
|
11
|
+
username: string;
|
|
12
|
+
name?: {
|
|
13
|
+
first?: string;
|
|
14
|
+
last?: string;
|
|
15
|
+
};
|
|
16
|
+
bio?: string;
|
|
17
|
+
avatar?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ManagedAccountManager {
|
|
20
|
+
userId: string;
|
|
21
|
+
role: 'owner' | 'admin' | 'editor';
|
|
22
|
+
addedAt: string;
|
|
23
|
+
addedBy?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ManagedAccount {
|
|
26
|
+
accountId: string;
|
|
27
|
+
ownerId: string;
|
|
28
|
+
managers: ManagedAccountManager[];
|
|
29
|
+
account?: User;
|
|
30
|
+
createdAt?: string;
|
|
31
|
+
updatedAt?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function OxyServicesManagedAccountsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
34
|
+
new (...args: any[]): {
|
|
35
|
+
/**
|
|
36
|
+
* Create a new managed account (sub-account).
|
|
37
|
+
*
|
|
38
|
+
* The server creates a User document with `isManagedAccount: true` and links
|
|
39
|
+
* it to the authenticated user as owner.
|
|
40
|
+
*/
|
|
41
|
+
createManagedAccount(data: CreateManagedAccountInput): Promise<ManagedAccount>;
|
|
42
|
+
/**
|
|
43
|
+
* List all accounts the authenticated user manages.
|
|
44
|
+
*/
|
|
45
|
+
getManagedAccounts(): Promise<ManagedAccount[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Get details for a specific managed account.
|
|
48
|
+
*/
|
|
49
|
+
getManagedAccountDetails(accountId: string): Promise<ManagedAccount>;
|
|
50
|
+
/**
|
|
51
|
+
* Update a managed account's profile data.
|
|
52
|
+
* Requires owner or admin role.
|
|
53
|
+
*/
|
|
54
|
+
updateManagedAccount(accountId: string, data: Partial<CreateManagedAccountInput>): Promise<ManagedAccount>;
|
|
55
|
+
/**
|
|
56
|
+
* Delete a managed account permanently.
|
|
57
|
+
* Requires owner role.
|
|
58
|
+
*/
|
|
59
|
+
deleteManagedAccount(accountId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Add a manager to a managed account.
|
|
62
|
+
* Requires owner or admin role on the account.
|
|
63
|
+
*
|
|
64
|
+
* @param accountId - The managed account to add the manager to
|
|
65
|
+
* @param userId - The user to grant management access
|
|
66
|
+
* @param role - The role to assign: 'admin' or 'editor'
|
|
67
|
+
*/
|
|
68
|
+
addManager(accountId: string, userId: string, role: "admin" | "editor"): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Remove a manager from a managed account.
|
|
71
|
+
* Requires owner role.
|
|
72
|
+
*
|
|
73
|
+
* @param accountId - The managed account
|
|
74
|
+
* @param userId - The manager to remove
|
|
75
|
+
*/
|
|
76
|
+
removeManager(accountId: string, userId: string): Promise<void>;
|
|
77
|
+
httpService: import("../HttpService").HttpService;
|
|
78
|
+
cloudURL: string;
|
|
79
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
80
|
+
__resetTokensForTests(): void;
|
|
81
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
82
|
+
getBaseURL(): string;
|
|
83
|
+
getClient(): import("../HttpService").HttpService;
|
|
84
|
+
getMetrics(): {
|
|
85
|
+
totalRequests: number;
|
|
86
|
+
successfulRequests: number;
|
|
87
|
+
failedRequests: number;
|
|
88
|
+
cacheHits: number;
|
|
89
|
+
cacheMisses: number;
|
|
90
|
+
averageResponseTime: number;
|
|
91
|
+
};
|
|
92
|
+
clearCache(): void;
|
|
93
|
+
clearCacheEntry(key: string): void;
|
|
94
|
+
getCacheStats(): {
|
|
95
|
+
size: number;
|
|
96
|
+
hits: number;
|
|
97
|
+
misses: number;
|
|
98
|
+
hitRate: number;
|
|
99
|
+
};
|
|
100
|
+
getCloudURL(): string;
|
|
101
|
+
setTokens(accessToken: string, refreshToken?: string): void;
|
|
102
|
+
clearTokens(): void;
|
|
103
|
+
_cachedUserId: string | null | undefined;
|
|
104
|
+
_cachedAccessToken: string | null;
|
|
105
|
+
getCurrentUserId(): string | null;
|
|
106
|
+
hasValidToken(): boolean;
|
|
107
|
+
getAccessToken(): string | null;
|
|
108
|
+
setActingAs(userId: string | null): void;
|
|
109
|
+
getActingAs(): string | null;
|
|
110
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
111
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
112
|
+
maxRetries?: number;
|
|
113
|
+
retryDelay?: number;
|
|
114
|
+
authTimeoutMs?: number;
|
|
115
|
+
}): Promise<T_1>;
|
|
116
|
+
validate(): Promise<boolean>;
|
|
117
|
+
handleError(error: unknown): Error;
|
|
118
|
+
healthCheck(): Promise<{
|
|
119
|
+
status: string;
|
|
120
|
+
users?: number;
|
|
121
|
+
timestamp?: string;
|
|
122
|
+
[key: string]: any;
|
|
123
|
+
}>;
|
|
124
|
+
};
|
|
125
|
+
} & T;
|
|
@@ -95,6 +95,8 @@ export declare function OxyServicesPaymentMixin<T extends typeof OxyServicesBase
|
|
|
95
95
|
getCurrentUserId(): string | null;
|
|
96
96
|
hasValidToken(): boolean;
|
|
97
97
|
getAccessToken(): string | null;
|
|
98
|
+
setActingAs(userId: string | null): void;
|
|
99
|
+
getActingAs(): string | null;
|
|
98
100
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
99
101
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
100
102
|
maxRetries?: number;
|
|
@@ -185,6 +185,8 @@ export declare function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBa
|
|
|
185
185
|
getCurrentUserId(): string | null;
|
|
186
186
|
hasValidToken(): boolean;
|
|
187
187
|
getAccessToken(): string | null;
|
|
188
|
+
setActingAs(userId: string | null): void;
|
|
189
|
+
getActingAs(): string | null;
|
|
188
190
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
189
191
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
190
192
|
maxRetries?: number;
|
|
@@ -106,6 +106,8 @@ export declare function OxyServicesPrivacyMixin<T extends typeof OxyServicesBase
|
|
|
106
106
|
getCurrentUserId(): string | null;
|
|
107
107
|
hasValidToken(): boolean;
|
|
108
108
|
getAccessToken(): string | null;
|
|
109
|
+
setActingAs(userId: string | null): void;
|
|
110
|
+
getActingAs(): string | null;
|
|
109
111
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
110
112
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
111
113
|
maxRetries?: number;
|
|
@@ -222,6 +222,8 @@ export declare function OxyServicesRedirectAuthMixin<T extends typeof OxyService
|
|
|
222
222
|
getCurrentUserId(): string | null;
|
|
223
223
|
hasValidToken(): boolean;
|
|
224
224
|
getAccessToken(): string | null;
|
|
225
|
+
setActingAs(userId: string | null): void;
|
|
226
|
+
getActingAs(): string | null;
|
|
225
227
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
226
228
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
227
229
|
maxRetries?: number;
|
|
@@ -62,6 +62,8 @@ export declare function OxyServicesSecurityMixin<T extends typeof OxyServicesBas
|
|
|
62
62
|
getCurrentUserId(): string | null;
|
|
63
63
|
hasValidToken(): boolean;
|
|
64
64
|
getAccessToken(): string | null;
|
|
65
|
+
setActingAs(userId: string | null): void;
|
|
66
|
+
getActingAs(): string | null;
|
|
65
67
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
66
68
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
67
69
|
maxRetries?: number;
|
|
@@ -88,6 +88,8 @@ export declare function OxyServicesTopicsMixin<T extends typeof OxyServicesBase>
|
|
|
88
88
|
getCurrentUserId(): string | null;
|
|
89
89
|
hasValidToken(): boolean;
|
|
90
90
|
getAccessToken(): string | null;
|
|
91
|
+
setActingAs(userId: string | null): void;
|
|
92
|
+
getActingAs(): string | null;
|
|
91
93
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
92
94
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
93
95
|
maxRetries?: number;
|
|
@@ -10,6 +10,18 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
10
10
|
* Get profile by username
|
|
11
11
|
*/
|
|
12
12
|
getProfileByUsername(username: string): Promise<User>;
|
|
13
|
+
/**
|
|
14
|
+
* Lightweight username lookup for login flows.
|
|
15
|
+
* Returns minimal public info: exists, color, avatar, displayName.
|
|
16
|
+
* Faster than getProfileByUsername — no stats, no formatting.
|
|
17
|
+
*/
|
|
18
|
+
lookupUsername(username: string): Promise<{
|
|
19
|
+
exists: boolean;
|
|
20
|
+
username: string;
|
|
21
|
+
color: string | null;
|
|
22
|
+
avatar: string | null;
|
|
23
|
+
displayName: string;
|
|
24
|
+
}>;
|
|
13
25
|
/**
|
|
14
26
|
* Search user profiles
|
|
15
27
|
*/
|
|
@@ -205,6 +217,8 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
205
217
|
getCurrentUserId(): string | null;
|
|
206
218
|
hasValidToken(): boolean;
|
|
207
219
|
getAccessToken(): string | null;
|
|
220
|
+
setActingAs(userId: string | null): void;
|
|
221
|
+
getActingAs(): string | null;
|
|
208
222
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
209
223
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
210
224
|
maxRetries?: number;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ApiError } from '../models/interfaces';
|
|
2
2
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
3
|
+
/**
|
|
4
|
+
* Result from the managed-accounts verification endpoint.
|
|
5
|
+
* Indicates whether a user is authorized to act as a given managed account.
|
|
6
|
+
*/
|
|
7
|
+
interface ActingAsVerification {
|
|
8
|
+
authorized: boolean;
|
|
9
|
+
role: 'owner' | 'admin' | 'editor';
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Service app metadata attached to requests authenticated with service tokens
|
|
5
13
|
*/
|
|
@@ -28,6 +36,18 @@ interface AuthMiddlewareOptions {
|
|
|
28
36
|
}
|
|
29
37
|
export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
30
38
|
new (...args: any[]): {
|
|
39
|
+
/** @internal In-memory cache for acting-as verification results (TTL: 5 min) */
|
|
40
|
+
_actingAsCache: Map<string, {
|
|
41
|
+
result: ActingAsVerification | null;
|
|
42
|
+
expiresAt: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Verify that a user is authorized to act as a managed account.
|
|
46
|
+
* Results are cached in-memory for 5 minutes to avoid repeated API calls.
|
|
47
|
+
*
|
|
48
|
+
* @internal Used by the auth() middleware — not part of the public API
|
|
49
|
+
*/
|
|
50
|
+
verifyActingAs(userId: string, accountId: string): Promise<ActingAsVerification | null>;
|
|
31
51
|
/**
|
|
32
52
|
* Fetch link metadata
|
|
33
53
|
*/
|
|
@@ -157,6 +177,8 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
|
|
|
157
177
|
getCurrentUserId(): string | null;
|
|
158
178
|
hasValidToken(): boolean;
|
|
159
179
|
getAccessToken(): string | null;
|
|
180
|
+
setActingAs(userId: string | null): void;
|
|
181
|
+
getActingAs(): string | null;
|
|
160
182
|
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
161
183
|
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
162
184
|
maxRetries?: number;
|
|
@@ -13,8 +13,12 @@ export declare function parallelWithErrorHandling<T>(operations: (() => Promise<
|
|
|
13
13
|
/**
|
|
14
14
|
* Retry an async operation with exponential backoff
|
|
15
15
|
*
|
|
16
|
-
* By default, does not retry on 4xx errors (client errors).
|
|
17
|
-
*
|
|
16
|
+
* By default, does not retry on 4xx errors (client errors). The default
|
|
17
|
+
* predicate accepts both the axios-style `error.response.status` and the
|
|
18
|
+
* flat `error.status` shape produced by {@link handleHttpError}, so callers
|
|
19
|
+
* never accidentally retry a deterministic client failure.
|
|
20
|
+
*
|
|
21
|
+
* Use the `shouldRetry` callback to customize retry behavior.
|
|
18
22
|
*/
|
|
19
23
|
export declare function retryAsync<T>(operation: () => Promise<T>, maxRetries?: number, baseDelay?: number, shouldRetry?: (error: any) => boolean): Promise<T>;
|
|
20
24
|
/**
|
package/package.json
CHANGED
package/src/HttpService.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { TTLCache, registerCacheForCleanup } from './utils/cache';
|
|
|
17
17
|
import { RequestDeduplicator, RequestQueue, SimpleLogger } from './utils/requestUtils';
|
|
18
18
|
import { retryAsync } from './utils/asyncUtils';
|
|
19
19
|
import { handleHttpError } from './utils/errorUtils';
|
|
20
|
-
import { isDev } from './shared/utils/debugUtils';
|
|
21
20
|
import { jwtDecode } from 'jwt-decode';
|
|
22
21
|
import { isNative, getPlatformOS } from './utils/platform';
|
|
23
22
|
import type { OxyConfig } from './models/interfaces';
|
|
@@ -131,6 +130,9 @@ export class HttpService {
|
|
|
131
130
|
private tokenRefreshCooldownUntil: number = 0;
|
|
132
131
|
private _onTokenRefreshed: ((accessToken: string) => void) | null = null;
|
|
133
132
|
|
|
133
|
+
// Acting-as identity for managed accounts
|
|
134
|
+
private _actingAsUserId: string | null = null;
|
|
135
|
+
|
|
134
136
|
// Performance monitoring
|
|
135
137
|
private requestMetrics = {
|
|
136
138
|
totalRequests: 0,
|
|
@@ -278,9 +280,12 @@ export class HttpService {
|
|
|
278
280
|
headers['X-Native-App'] = 'true';
|
|
279
281
|
}
|
|
280
282
|
|
|
281
|
-
// Debug logging for CSRF issues
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
// Debug logging for CSRF issues — routed through the SimpleLogger so
|
|
284
|
+
// it only fires when consumers opt in via `enableLogging`. Previously
|
|
285
|
+
// this was a bare console.log that leaked noise into every host app's
|
|
286
|
+
// stdout in development.
|
|
287
|
+
if (isStateChangingMethod) {
|
|
288
|
+
this.logger.debug('CSRF Debug:', {
|
|
284
289
|
url,
|
|
285
290
|
method,
|
|
286
291
|
isNativeApp,
|
|
@@ -291,6 +296,11 @@ export class HttpService {
|
|
|
291
296
|
});
|
|
292
297
|
}
|
|
293
298
|
|
|
299
|
+
// Add X-Acting-As header for managed account identity delegation
|
|
300
|
+
if (this._actingAsUserId) {
|
|
301
|
+
headers['X-Acting-As'] = this._actingAsUserId;
|
|
302
|
+
}
|
|
303
|
+
|
|
294
304
|
// Merge custom headers if provided
|
|
295
305
|
if (config.headers) {
|
|
296
306
|
Object.entries(config.headers).forEach(([key, value]) => {
|
|
@@ -516,14 +526,14 @@ export class HttpService {
|
|
|
516
526
|
// Return cached token if available
|
|
517
527
|
const cachedToken = this.tokenStore.getCsrfToken();
|
|
518
528
|
if (cachedToken) {
|
|
519
|
-
|
|
529
|
+
this.logger.debug('Using cached CSRF token');
|
|
520
530
|
return cachedToken;
|
|
521
531
|
}
|
|
522
532
|
|
|
523
533
|
// Deduplicate concurrent CSRF token fetches
|
|
524
534
|
const existingPromise = this.tokenStore.getCsrfTokenFetchPromise();
|
|
525
535
|
if (existingPromise) {
|
|
526
|
-
|
|
536
|
+
this.logger.debug('Waiting for existing CSRF fetch');
|
|
527
537
|
return existingPromise;
|
|
528
538
|
}
|
|
529
539
|
|
|
@@ -531,7 +541,7 @@ export class HttpService {
|
|
|
531
541
|
const maxAttempts = 2;
|
|
532
542
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
533
543
|
try {
|
|
534
|
-
|
|
544
|
+
this.logger.debug('Fetching CSRF token from:', `${this.baseURL}/csrf-token`, `(attempt ${attempt})`);
|
|
535
545
|
|
|
536
546
|
// Use AbortController for timeout (more compatible than AbortSignal.timeout)
|
|
537
547
|
const controller = new AbortController();
|
|
@@ -546,11 +556,11 @@ export class HttpService {
|
|
|
546
556
|
|
|
547
557
|
clearTimeout(timeoutId);
|
|
548
558
|
|
|
549
|
-
|
|
559
|
+
this.logger.debug('CSRF fetch response:', response.status, response.ok);
|
|
550
560
|
|
|
551
561
|
if (response.ok) {
|
|
552
562
|
const data = await response.json() as { csrfToken?: string };
|
|
553
|
-
|
|
563
|
+
this.logger.debug('CSRF response data:', data);
|
|
554
564
|
const token = data.csrfToken || null;
|
|
555
565
|
this.tokenStore.setCsrfToken(token);
|
|
556
566
|
this.logger.debug('CSRF token fetched');
|
|
@@ -565,10 +575,10 @@ export class HttpService {
|
|
|
565
575
|
return headerToken;
|
|
566
576
|
}
|
|
567
577
|
|
|
568
|
-
|
|
578
|
+
this.logger.debug('CSRF fetch failed with status:', response.status);
|
|
569
579
|
this.logger.warn('Failed to fetch CSRF token:', response.status);
|
|
570
580
|
} catch (error) {
|
|
571
|
-
|
|
581
|
+
this.logger.debug('CSRF fetch error:', error);
|
|
572
582
|
this.logger.warn('CSRF token fetch error:', error);
|
|
573
583
|
}
|
|
574
584
|
// Wait before retry (500ms)
|
|
@@ -706,6 +716,15 @@ export class HttpService {
|
|
|
706
716
|
return this.request<T>({ method: 'DELETE', url, ...config });
|
|
707
717
|
}
|
|
708
718
|
|
|
719
|
+
// Acting-as identity management (managed accounts)
|
|
720
|
+
setActingAs(userId: string | null): void {
|
|
721
|
+
this._actingAsUserId = userId;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
getActingAs(): string | null {
|
|
725
|
+
return this._actingAsUserId;
|
|
726
|
+
}
|
|
727
|
+
|
|
709
728
|
// Token management
|
|
710
729
|
setTokens(accessToken: string, refreshToken = ''): void {
|
|
711
730
|
this.tokenStore.setTokens(accessToken, refreshToken);
|
package/src/OxyServices.base.ts
CHANGED
|
@@ -182,6 +182,29 @@ export class OxyServicesBase {
|
|
|
182
182
|
return this.httpService.getAccessToken();
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Set the acting-as identity for managed accounts.
|
|
187
|
+
*
|
|
188
|
+
* When set, all subsequent API requests will include the `X-Acting-As` header,
|
|
189
|
+
* causing the server to attribute actions to the managed account. The
|
|
190
|
+
* authenticated user must be an authorized manager of the target account.
|
|
191
|
+
*
|
|
192
|
+
* Pass `null` to clear and revert to the authenticated user's own identity.
|
|
193
|
+
*
|
|
194
|
+
* @param userId - The managed account user ID, or null to clear
|
|
195
|
+
*/
|
|
196
|
+
public setActingAs(userId: string | null): void {
|
|
197
|
+
this.httpService.setActingAs(userId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get the current acting-as identity (managed account user ID), or null
|
|
202
|
+
* if operating as the authenticated user's own identity.
|
|
203
|
+
*/
|
|
204
|
+
public getActingAs(): string | null {
|
|
205
|
+
return this.httpService.getActingAs();
|
|
206
|
+
}
|
|
207
|
+
|
|
185
208
|
/**
|
|
186
209
|
* Wait for authentication to be ready
|
|
187
210
|
*
|
package/src/crypto/keyManager.ts
CHANGED
|
@@ -52,7 +52,7 @@ async function initSecureStore(): Promise<typeof import('expo-secure-store')> {
|
|
|
52
52
|
try {
|
|
53
53
|
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
54
54
|
const moduleName = 'expo-secure-store';
|
|
55
|
-
SecureStore = await import(moduleName);
|
|
55
|
+
SecureStore = await import(/* @vite-ignore */ moduleName);
|
|
56
56
|
} catch (error) {
|
|
57
57
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
58
|
throw new Error(`Failed to load expo-secure-store: ${errorMessage}. Make sure expo-secure-store is installed and properly configured.`);
|
|
@@ -76,7 +76,7 @@ async function initExpoCrypto(): Promise<typeof import('expo-crypto')> {
|
|
|
76
76
|
if (!ExpoCrypto) {
|
|
77
77
|
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
78
78
|
const moduleName = 'expo-crypto';
|
|
79
|
-
ExpoCrypto = await import(moduleName);
|
|
79
|
+
ExpoCrypto = await import(/* @vite-ignore */ moduleName);
|
|
80
80
|
}
|
|
81
81
|
return ExpoCrypto!;
|
|
82
82
|
}
|
|
@@ -105,7 +105,7 @@ async function getSecureRandomBytes(length: number): Promise<Uint8Array> {
|
|
|
105
105
|
// Variable indirection prevents bundlers (Vite, webpack) from statically resolving this
|
|
106
106
|
try {
|
|
107
107
|
const cryptoModuleName = 'crypto';
|
|
108
|
-
const nodeCrypto = await import(cryptoModuleName);
|
|
108
|
+
const nodeCrypto = await import(/* @vite-ignore */ cryptoModuleName);
|
|
109
109
|
return new Uint8Array(nodeCrypto.randomBytes(length));
|
|
110
110
|
} catch (error) {
|
|
111
111
|
// Fallback to expo-crypto if Node crypto fails
|
package/src/crypto/polyfill.ts
CHANGED
|
@@ -44,7 +44,7 @@ function startExpoCryptoLoad(): void {
|
|
|
44
44
|
expoCryptoLoadPromise = (async () => {
|
|
45
45
|
try {
|
|
46
46
|
const moduleName = 'expo-crypto';
|
|
47
|
-
expoCryptoModule = await import(moduleName);
|
|
47
|
+
expoCryptoModule = await import(/* @vite-ignore */ moduleName);
|
|
48
48
|
} catch {
|
|
49
49
|
// expo-crypto not available — expected in non-RN environments
|
|
50
50
|
}
|