@oxyhq/core 3.1.0 → 3.4.0
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/AuthManager.js +14 -3
- package/dist/cjs/HttpService.js +89 -0
- package/dist/cjs/OxyServices.js +2 -1
- package/dist/cjs/constants/version.js +1 -1
- package/dist/cjs/i18n/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/es-ES.json +44 -44
- package/dist/cjs/i18n/locales/locales/en-US.json +44 -44
- package/dist/cjs/i18n/locales/locales/es-ES.json +44 -44
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/mixins/OxyServices.applications.js +33 -3
- package/dist/cjs/mixins/OxyServices.reputation.js +244 -0
- package/dist/cjs/mixins/OxyServices.workspaces.js +146 -0
- package/dist/cjs/mixins/index.js +4 -2
- package/dist/cjs/utils/accountUtils.js +12 -5
- package/dist/cjs/utils/ssoReturn.js +80 -33
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +14 -3
- package/dist/esm/HttpService.js +89 -0
- package/dist/esm/OxyServices.js +2 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/i18n/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/es-ES.json +44 -44
- package/dist/esm/i18n/locales/locales/en-US.json +44 -44
- package/dist/esm/i18n/locales/locales/es-ES.json +44 -44
- package/dist/esm/index.js +4 -0
- package/dist/esm/mixins/OxyServices.applications.js +33 -3
- package/dist/esm/mixins/OxyServices.reputation.js +241 -0
- package/dist/esm/mixins/OxyServices.workspaces.js +143 -0
- package/dist/esm/mixins/index.js +4 -2
- package/dist/esm/utils/accountUtils.js +12 -5
- package/dist/esm/utils/ssoReturn.js +80 -33
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +57 -0
- package/dist/types/OxyServices.d.ts +2 -1
- package/dist/types/constants/version.d.ts +2 -2
- package/dist/types/index.d.ts +4 -2
- package/dist/types/mixins/OxyServices.applications.d.ts +86 -10
- package/dist/types/mixins/OxyServices.features.d.ts +0 -1
- package/dist/types/mixins/OxyServices.reputation.d.ts +436 -0
- package/dist/types/mixins/OxyServices.workspaces.d.ts +205 -0
- package/dist/types/mixins/index.d.ts +3 -2
- package/dist/types/models/interfaces.d.ts +24 -26
- package/dist/types/utils/accountUtils.d.ts +17 -4
- package/dist/types/utils/ssoReturn.d.ts +30 -9
- package/package.json +2 -1
- package/src/AuthManager.ts +14 -3
- package/src/HttpService.ts +91 -0
- package/src/OxyServices.ts +2 -1
- package/src/__tests__/authManager.cookiePath.test.ts +49 -0
- package/src/__tests__/httpServiceCache.test.ts +198 -0
- package/src/constants/version.ts +1 -1
- package/src/i18n/locales/en-US.json +44 -44
- package/src/i18n/locales/es-ES.json +44 -44
- package/src/index.ts +51 -4
- package/src/mixins/OxyServices.applications.ts +103 -5
- package/src/mixins/OxyServices.auth.ts +2 -1
- package/src/mixins/OxyServices.features.ts +0 -1
- package/src/mixins/OxyServices.reputation.ts +674 -0
- package/src/mixins/OxyServices.workspaces.ts +315 -0
- package/src/mixins/__tests__/reputation.test.ts +408 -0
- package/src/mixins/index.ts +6 -3
- package/src/models/interfaces.ts +25 -32
- package/src/utils/__tests__/accountUtils.test.ts +142 -0
- package/src/utils/__tests__/consumeSsoReturn.test.ts +229 -37
- package/src/utils/accountUtils.ts +20 -5
- package/src/utils/ssoReturn.ts +98 -37
- package/dist/cjs/mixins/OxyServices.developer.js +0 -97
- package/dist/cjs/mixins/OxyServices.karma.js +0 -108
- package/dist/esm/mixins/OxyServices.developer.js +0 -94
- package/dist/esm/mixins/OxyServices.karma.js +0 -105
- package/dist/types/mixins/OxyServices.developer.d.ts +0 -106
- package/dist/types/mixins/OxyServices.karma.d.ts +0 -92
- package/src/mixins/OxyServices.karma.ts +0 -111
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesReputationMixin = OxyServicesReputationMixin;
|
|
4
|
+
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
|
+
/** Cache-key prefix for every cached `GET /reputation/...` response. */
|
|
6
|
+
const REPUTATION_CACHE_PREFIX = 'GET:/reputation/';
|
|
7
|
+
function OxyServicesReputationMixin(Base) {
|
|
8
|
+
return class extends Base {
|
|
9
|
+
constructor(...args) {
|
|
10
|
+
super(...args);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get a user's cached reputation balance — derived totals, per-category
|
|
14
|
+
* breakdown, trust tier, capped influence weights, and reliability signals.
|
|
15
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
16
|
+
*/
|
|
17
|
+
async getReputationBalance(userId) {
|
|
18
|
+
try {
|
|
19
|
+
return await this.makeRequest('GET', `/reputation/${encodeURIComponent(userId)}/balance`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw this.handleError(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the reputation leaderboard, ordered by lifetime total descending.
|
|
27
|
+
* @param limit - Page size (server-capped).
|
|
28
|
+
* @param offset - Page offset.
|
|
29
|
+
*/
|
|
30
|
+
async getReputationLeaderboard(limit, offset) {
|
|
31
|
+
try {
|
|
32
|
+
const params = {};
|
|
33
|
+
if (limit !== undefined)
|
|
34
|
+
params.limit = limit;
|
|
35
|
+
if (offset !== undefined)
|
|
36
|
+
params.offset = offset;
|
|
37
|
+
const res = await this.makeRequest('GET', '/reputation/leaderboard', Object.keys(params).length > 0 ? params : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG });
|
|
38
|
+
return res.data ?? [];
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw this.handleError(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* List the enabled reputation rules (for client display).
|
|
46
|
+
*/
|
|
47
|
+
async getReputationRules() {
|
|
48
|
+
try {
|
|
49
|
+
const res = await this.makeRequest('GET', '/reputation/rules', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.EXTRA_LONG });
|
|
50
|
+
return res.rules ?? [];
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw this.handleError(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a user's paginated reputation ledger, newest first (auth required).
|
|
58
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
59
|
+
* @param limit - Page size (server-capped).
|
|
60
|
+
* @param offset - Page offset.
|
|
61
|
+
*/
|
|
62
|
+
async getReputationTransactions(userId, limit, offset) {
|
|
63
|
+
try {
|
|
64
|
+
const params = {};
|
|
65
|
+
if (limit !== undefined)
|
|
66
|
+
params.limit = limit;
|
|
67
|
+
if (offset !== undefined)
|
|
68
|
+
params.offset = offset;
|
|
69
|
+
const res = await this.makeRequest('GET', `/reputation/${encodeURIComponent(userId)}/transactions`, Object.keys(params).length > 0 ? params : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
70
|
+
return res.data ?? [];
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw this.handleError(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get a user's capped influence weight for a given context (auth required).
|
|
78
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
79
|
+
* @param context - The weight axis to read (defaults server-side to `default`).
|
|
80
|
+
*/
|
|
81
|
+
async getReputationInfluence(userId, context) {
|
|
82
|
+
try {
|
|
83
|
+
return await this.makeRequest('GET', `/reputation/${encodeURIComponent(userId)}/influence`, context ? { context } : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw this.handleError(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Award (or penalise) reputation to a user by `actionType`. Restricted to
|
|
91
|
+
* service tokens and platform staff. Invalidates cached reputation reads.
|
|
92
|
+
* @param input - The award payload (subject, action, source, target, etc.).
|
|
93
|
+
*/
|
|
94
|
+
async awardReputation(input) {
|
|
95
|
+
try {
|
|
96
|
+
const res = await this.makeRequest('POST', '/reputation/award', input, { cache: false });
|
|
97
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
98
|
+
return res.transaction;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw this.handleError(error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Open a dispute against a transaction (auth required; the disputer is the
|
|
106
|
+
* authenticated user and must own the transaction).
|
|
107
|
+
* @param input - The transaction id, reason, and optional evidence.
|
|
108
|
+
*/
|
|
109
|
+
async createReputationDispute(input) {
|
|
110
|
+
try {
|
|
111
|
+
const res = await this.makeRequest('POST', '/reputation/disputes', input, { cache: false });
|
|
112
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
113
|
+
return res.dispute;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw this.handleError(error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* List a user's own reputation disputes (auth required; the caller must be
|
|
121
|
+
* the subject or platform staff).
|
|
122
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
123
|
+
* @param limit - Page size (server-capped).
|
|
124
|
+
* @param offset - Page offset.
|
|
125
|
+
*/
|
|
126
|
+
async getUserReputationDisputes(userId, limit, offset) {
|
|
127
|
+
try {
|
|
128
|
+
const params = {};
|
|
129
|
+
if (limit !== undefined)
|
|
130
|
+
params.limit = limit;
|
|
131
|
+
if (offset !== undefined)
|
|
132
|
+
params.offset = offset;
|
|
133
|
+
const res = await this.makeRequest('GET', `/reputation/${encodeURIComponent(userId)}/disputes`, Object.keys(params).length > 0 ? params : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
134
|
+
return res.data ?? [];
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw this.handleError(error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// =========================================================================
|
|
141
|
+
// STAFF / ADMIN METHODS (require staff privileges server-side)
|
|
142
|
+
// =========================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Create or update a reputation rule, keyed by `actionType` (staff only).
|
|
145
|
+
* Invalidates the cached rule list.
|
|
146
|
+
* @param input - The rule definition.
|
|
147
|
+
*/
|
|
148
|
+
async upsertReputationRule(input) {
|
|
149
|
+
try {
|
|
150
|
+
const res = await this.makeRequest('POST', '/reputation/rules', input, { cache: false });
|
|
151
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
152
|
+
return res.rule;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw this.handleError(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Reverse a transaction (staff only): mark the original `reversed` and append
|
|
160
|
+
* a compensating `active` reversal with negated points. Invalidates cached
|
|
161
|
+
* reputation reads.
|
|
162
|
+
* @param transactionId - The transaction's id.
|
|
163
|
+
* @param input - Optional reason for the reversal.
|
|
164
|
+
*/
|
|
165
|
+
async reverseReputationTransaction(transactionId, input) {
|
|
166
|
+
try {
|
|
167
|
+
const res = await this.makeRequest('POST', `/reputation/transactions/${encodeURIComponent(transactionId)}/reverse`, input ?? {}, { cache: false });
|
|
168
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
169
|
+
return res;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
throw this.handleError(error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Void a transaction (staff only): mark it `voided` so it is excluded from
|
|
177
|
+
* the balance, with NO compensating entry. Invalidates cached reputation
|
|
178
|
+
* reads.
|
|
179
|
+
* @param transactionId - The transaction's id.
|
|
180
|
+
* @param input - Optional reason for the void.
|
|
181
|
+
*/
|
|
182
|
+
async voidReputationTransaction(transactionId, input) {
|
|
183
|
+
try {
|
|
184
|
+
const res = await this.makeRequest('POST', `/reputation/transactions/${encodeURIComponent(transactionId)}/void`, input ?? {}, { cache: false });
|
|
185
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
186
|
+
return res.transaction;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw this.handleError(error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Force a recompute of a user's balance snapshot from their active ledger
|
|
194
|
+
* (staff only). Invalidates cached reputation reads.
|
|
195
|
+
* @param userId - The subject user's `_id` or publicKey.
|
|
196
|
+
*/
|
|
197
|
+
async recalculateReputation(userId) {
|
|
198
|
+
try {
|
|
199
|
+
const res = await this.makeRequest('POST', `/reputation/${encodeURIComponent(userId)}/recalculate`, undefined, { cache: false });
|
|
200
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
201
|
+
return res;
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
throw this.handleError(error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get the open dispute queue across all users (staff only).
|
|
209
|
+
* @param limit - Page size (server-capped).
|
|
210
|
+
* @param offset - Page offset.
|
|
211
|
+
*/
|
|
212
|
+
async getReputationDisputeQueue(limit, offset) {
|
|
213
|
+
try {
|
|
214
|
+
const params = {};
|
|
215
|
+
if (limit !== undefined)
|
|
216
|
+
params.limit = limit;
|
|
217
|
+
if (offset !== undefined)
|
|
218
|
+
params.offset = offset;
|
|
219
|
+
const res = await this.makeRequest('GET', '/reputation/disputes', Object.keys(params).length > 0 ? params : undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
220
|
+
return res.data ?? [];
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
throw this.handleError(error);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Resolve a dispute (staff only). Accepting reverses the disputed
|
|
228
|
+
* transaction; rejecting restores it to `active`. Invalidates cached
|
|
229
|
+
* reputation reads.
|
|
230
|
+
* @param disputeId - The dispute's id.
|
|
231
|
+
* @param input - The resolution (`accepted` or `rejected`).
|
|
232
|
+
*/
|
|
233
|
+
async resolveReputationDispute(disputeId, input) {
|
|
234
|
+
try {
|
|
235
|
+
const res = await this.makeRequest('POST', `/reputation/disputes/${encodeURIComponent(disputeId)}/resolve`, input, { cache: false });
|
|
236
|
+
this.clearCacheByPrefix(REPUTATION_CACHE_PREFIX);
|
|
237
|
+
return res.dispute;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
throw this.handleError(error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesWorkspacesMixin = OxyServicesWorkspacesMixin;
|
|
4
|
+
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
|
+
function OxyServicesWorkspacesMixin(Base) {
|
|
6
|
+
return class extends Base {
|
|
7
|
+
constructor(...args) {
|
|
8
|
+
super(...args);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* List workspaces the current user is an active member of.
|
|
12
|
+
*/
|
|
13
|
+
async getWorkspaces() {
|
|
14
|
+
try {
|
|
15
|
+
const res = await this.makeRequest('GET', '/workspaces', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
16
|
+
return res.workspaces ?? [];
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw this.handleError(error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a new team workspace. The caller becomes its `owner`.
|
|
24
|
+
* @param data - Workspace configuration.
|
|
25
|
+
*/
|
|
26
|
+
async createWorkspace(data) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await this.makeRequest('POST', '/workspaces', data, { cache: false });
|
|
29
|
+
return res.workspace;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw this.handleError(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetch a single workspace by id.
|
|
37
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
38
|
+
*/
|
|
39
|
+
async getWorkspace(workspaceId) {
|
|
40
|
+
try {
|
|
41
|
+
const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.LONG });
|
|
42
|
+
return res.workspace;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw this.handleError(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Update a workspace's mutable fields.
|
|
50
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
51
|
+
* @param data - Subset of updatable fields.
|
|
52
|
+
*/
|
|
53
|
+
async updateWorkspace(workspaceId, data) {
|
|
54
|
+
try {
|
|
55
|
+
const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}`, data, { cache: false });
|
|
56
|
+
return res.workspace;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw this.handleError(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Soft-delete a workspace (owner only).
|
|
64
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
65
|
+
*/
|
|
66
|
+
async deleteWorkspace(workspaceId) {
|
|
67
|
+
try {
|
|
68
|
+
return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: false });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw this.handleError(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* List members of a workspace.
|
|
76
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
77
|
+
*/
|
|
78
|
+
async getWorkspaceMembers(workspaceId) {
|
|
79
|
+
try {
|
|
80
|
+
const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}/members`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
81
|
+
return res.members ?? [];
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw this.handleError(error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Add a member to a workspace.
|
|
89
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
90
|
+
* @param data - Target user's username or email and role (never `owner`).
|
|
91
|
+
* The server resolves `usernameOrEmail` to a user; an unknown value yields
|
|
92
|
+
* a 404 "User not found".
|
|
93
|
+
*/
|
|
94
|
+
async inviteWorkspaceMember(workspaceId, data) {
|
|
95
|
+
try {
|
|
96
|
+
const res = await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/members`, data, { cache: false });
|
|
97
|
+
return res.member;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw this.handleError(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Change a member's role.
|
|
105
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
106
|
+
* @param memberId - The member's Mongo `_id`.
|
|
107
|
+
* @param data - New role (never `owner`).
|
|
108
|
+
*/
|
|
109
|
+
async updateWorkspaceMember(workspaceId, memberId, data) {
|
|
110
|
+
try {
|
|
111
|
+
const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, data, { cache: false });
|
|
112
|
+
return res.member;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw this.handleError(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove a member from a workspace.
|
|
120
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
121
|
+
* @param memberId - The member's Mongo `_id`.
|
|
122
|
+
*/
|
|
123
|
+
async removeWorkspaceMember(workspaceId, memberId) {
|
|
124
|
+
try {
|
|
125
|
+
return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, undefined, { cache: false });
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw this.handleError(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Transfer ownership of a workspace to another member (owner only).
|
|
133
|
+
* Demotes the current owner and promotes the target to `owner`.
|
|
134
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
135
|
+
* @param data - Target user id.
|
|
136
|
+
*/
|
|
137
|
+
async transferWorkspaceOwnership(workspaceId, data) {
|
|
138
|
+
try {
|
|
139
|
+
return await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/transfer-ownership`, data, { cache: false });
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw this.handleError(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
package/dist/cjs/mixins/index.js
CHANGED
|
@@ -18,9 +18,10 @@ const OxyServices_user_1 = require("./OxyServices.user");
|
|
|
18
18
|
const OxyServices_privacy_1 = require("./OxyServices.privacy");
|
|
19
19
|
const OxyServices_language_1 = require("./OxyServices.language");
|
|
20
20
|
const OxyServices_payment_1 = require("./OxyServices.payment");
|
|
21
|
-
const
|
|
21
|
+
const OxyServices_reputation_1 = require("./OxyServices.reputation");
|
|
22
22
|
const OxyServices_assets_1 = require("./OxyServices.assets");
|
|
23
23
|
const OxyServices_applications_1 = require("./OxyServices.applications");
|
|
24
|
+
const OxyServices_workspaces_1 = require("./OxyServices.workspaces");
|
|
24
25
|
const OxyServices_location_1 = require("./OxyServices.location");
|
|
25
26
|
const OxyServices_analytics_1 = require("./OxyServices.analytics");
|
|
26
27
|
const OxyServices_devices_1 = require("./OxyServices.devices");
|
|
@@ -62,9 +63,10 @@ const MIXIN_PIPELINE = [
|
|
|
62
63
|
// Feature mixins
|
|
63
64
|
OxyServices_language_1.OxyServicesLanguageMixin,
|
|
64
65
|
OxyServices_payment_1.OxyServicesPaymentMixin,
|
|
65
|
-
|
|
66
|
+
OxyServices_reputation_1.OxyServicesReputationMixin,
|
|
66
67
|
OxyServices_assets_1.OxyServicesAssetsMixin,
|
|
67
68
|
OxyServices_applications_1.OxyServicesApplicationsMixin,
|
|
69
|
+
OxyServices_workspaces_1.OxyServicesWorkspacesMixin,
|
|
68
70
|
OxyServices_location_1.OxyServicesLocationMixin,
|
|
69
71
|
OxyServices_analytics_1.OxyServicesAnalyticsMixin,
|
|
70
72
|
OxyServices_devices_1.OxyServicesDevicesMixin,
|
|
@@ -21,11 +21,16 @@ exports.formatPublicKeyHandle = formatPublicKeyHandle;
|
|
|
21
21
|
* Resolve a friendly display name for a user.
|
|
22
22
|
*
|
|
23
23
|
* Order of preference:
|
|
24
|
-
* 1. `name.full`, or composed `name.first name.last`
|
|
24
|
+
* 1. `name.full`, or composed `name.first name.last` (FIRST-NAME-ONLY SAFE —
|
|
25
|
+
* a user with only a first name resolves to that first name, never to the
|
|
26
|
+
* lowercase username; this is the exact drift bug the auth app hit).
|
|
25
27
|
* 2. `name` (when stored as a plain string)
|
|
26
|
-
* 3. `username`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* 3. `displayName` (server `displayName` virtual — `username || truncatedKey`).
|
|
29
|
+
* Placed AFTER the structured name on purpose: the server virtual ignores
|
|
30
|
+
* `name`, so preferring it first would re-introduce the first-only bug.
|
|
31
|
+
* 4. `username`
|
|
32
|
+
* 5. `Account 0x12345678…` (derived from publicKey, when present)
|
|
33
|
+
* 6. Translated fallback (e.g. "Unnamed")
|
|
29
34
|
*
|
|
30
35
|
* The translation key `common.unnamed` is used for the final fallback. If the
|
|
31
36
|
* caller does not pass a locale, the default English translation is used.
|
|
@@ -33,7 +38,7 @@ exports.formatPublicKeyHandle = formatPublicKeyHandle;
|
|
|
33
38
|
const getAccountDisplayName = (user, locale) => {
|
|
34
39
|
if (!user)
|
|
35
40
|
return (0, i18n_1.translate)(locale, 'common.unnamed');
|
|
36
|
-
const { name, username, publicKey } = user;
|
|
41
|
+
const { name, displayName, username, publicKey } = user;
|
|
37
42
|
if (name && typeof name === 'object') {
|
|
38
43
|
if (typeof name.full === 'string' && name.full.trim())
|
|
39
44
|
return name.full.trim();
|
|
@@ -46,6 +51,8 @@ const getAccountDisplayName = (user, locale) => {
|
|
|
46
51
|
else if (typeof name === 'string' && name.trim()) {
|
|
47
52
|
return name.trim();
|
|
48
53
|
}
|
|
54
|
+
if (typeof displayName === 'string' && displayName.trim())
|
|
55
|
+
return displayName.trim();
|
|
49
56
|
if (typeof username === 'string' && username.trim())
|
|
50
57
|
return username.trim();
|
|
51
58
|
if (typeof publicKey === 'string' && publicKey.length > 0) {
|
|
@@ -96,14 +96,22 @@ function parseSsoReturnFragment(hash) {
|
|
|
96
96
|
* treated exactly like "no session" (never loops, never rethrows).
|
|
97
97
|
* - On EVERY consumed outcome (ok, none, error, state-mismatch, no-code,
|
|
98
98
|
* failed-exchange, no-sessionId) — not just ok — if the page landed on
|
|
99
|
-
* {@link SSO_CALLBACK_PATH}, the
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
99
|
+
* {@link SSO_CALLBACK_PATH}, the user is taken to a same-origin TARGET so
|
|
100
|
+
* they are never stranded on the internal callback path (which is an
|
|
101
|
+
* unregistered route in every consumer router → a hard 404). The target is
|
|
102
|
+
* the stored DEST when it parses as same-origin (an attacker-planted
|
|
103
|
+
* cross-origin / protocol-relative dest is rejected), ELSE the app root
|
|
104
|
+
* (`origin + '/'`). The DEST key is removed unconditionally.
|
|
105
|
+
* - For the `ok` outcome the target is applied via a SOFT
|
|
106
|
+
* `history.replaceState` + synthetic `popstate` so the freshly exchanged
|
|
107
|
+
* in-memory session the provider is about to commit is preserved (no
|
|
108
|
+
* reload). `popstate` is dispatched only on the `ok` same-origin restore.
|
|
109
|
+
* - For every NON-`ok` outcome there is no in-memory session to preserve, and
|
|
110
|
+
* the consumer router has ALREADY synchronously rendered its 404 for the
|
|
111
|
+
* unregistered callback route — a soft replaceState+popstate does not
|
|
112
|
+
* reliably make it re-resolve. So these outcomes perform a HARD
|
|
113
|
+
* full-document navigation to the target (`hardRedirect`), which is both
|
|
114
|
+
* safe (nothing to lose) and guaranteed to clear the 404 in every router.
|
|
107
115
|
*
|
|
108
116
|
* Total: this function NEVER throws. Off-web it is a no-op returning `null`.
|
|
109
117
|
*
|
|
@@ -137,6 +145,17 @@ async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
137
145
|
window.dispatchEvent(new Event('popstate'));
|
|
138
146
|
}
|
|
139
147
|
});
|
|
148
|
+
// Default: a hard, full-document navigation used to leave the callback path
|
|
149
|
+
// on non-`ok` outcomes. Feature-detected end to end so it never throws in any
|
|
150
|
+
// environment (SSR / native / a stubbed location without `replace`).
|
|
151
|
+
const hardRedirect = deps.hardRedirect ??
|
|
152
|
+
((url) => {
|
|
153
|
+
if (typeof window !== 'undefined' &&
|
|
154
|
+
window.location &&
|
|
155
|
+
typeof window.location.replace === 'function') {
|
|
156
|
+
window.location.replace(url);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
140
159
|
const ret = parseSsoReturnFragment(location.hash);
|
|
141
160
|
if (!ret) {
|
|
142
161
|
// Not an oxy_sso fragment — nothing to do (do NOT touch any flags).
|
|
@@ -159,42 +178,62 @@ async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
159
178
|
// even if some consumer path skipped setting it pre-bounce.
|
|
160
179
|
storage.setItem((0, ssoBounce_1.ssoAttemptedKey)(origin), '1');
|
|
161
180
|
};
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
// Compute the same-origin TARGET to leave the callback path for. Returns the
|
|
182
|
+
// stored DEST when present AND it parses as same-origin (never honour a
|
|
183
|
+
// cross-origin / protocol-relative dest that could have been planted to
|
|
184
|
+
// redirect the user), ELSE the app root (`origin + '/'`) so the user is never
|
|
185
|
+
// stranded on the internal callback path even when no dest was stored. The
|
|
186
|
+
// DEST key is removed unconditionally. Returns the relative path+search+hash
|
|
187
|
+
// (so it can be fed to either `history.replaceState` or a `hardRedirect`),
|
|
188
|
+
// or `null` when the page is not on the callback path (nothing to leave).
|
|
189
|
+
const consumeCallbackTarget = () => {
|
|
190
|
+
if (location.pathname !== ssoBounce_1.SSO_CALLBACK_PATH) {
|
|
191
|
+
// Not on the callback path — still drop the dest key (consumed) but there
|
|
192
|
+
// is nothing to navigate away from.
|
|
193
|
+
storage.removeItem((0, ssoBounce_1.ssoDestKey)(origin));
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const dest = storage.getItem((0, ssoBounce_1.ssoDestKey)(origin));
|
|
197
|
+
storage.removeItem((0, ssoBounce_1.ssoDestKey)(origin));
|
|
198
|
+
if (dest) {
|
|
199
|
+
try {
|
|
200
|
+
const destUrl = new URL(dest, origin);
|
|
201
|
+
if (destUrl.origin === origin) {
|
|
202
|
+
return destUrl.pathname + destUrl.search + destUrl.hash;
|
|
181
203
|
}
|
|
182
204
|
}
|
|
205
|
+
catch {
|
|
206
|
+
// Malformed stored destination — fall through to the app-root fallback.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// No dest, a cross-origin/protocol-relative dest, or an unparseable dest:
|
|
210
|
+
// fall back to the app root so the router always leaves the 404.
|
|
211
|
+
return '/';
|
|
212
|
+
};
|
|
213
|
+
// Non-`ok` outcomes: there is no in-memory session to preserve, and the
|
|
214
|
+
// consumer router has already rendered its 404 for the unregistered callback
|
|
215
|
+
// route — a soft replaceState+popstate does not reliably make it re-resolve.
|
|
216
|
+
// Perform a HARD full-document navigation to the target (safe: nothing to
|
|
217
|
+
// lose; guaranteed: every router leaves the 404). Off the callback path this
|
|
218
|
+
// is a no-op (target is null).
|
|
219
|
+
const leaveCallbackHard = () => {
|
|
220
|
+
const target = consumeCallbackTarget();
|
|
221
|
+
if (target !== null) {
|
|
222
|
+
hardRedirect(origin + target);
|
|
183
223
|
}
|
|
184
|
-
storage.removeItem((0, ssoBounce_1.ssoDestKey)(origin));
|
|
185
224
|
};
|
|
186
225
|
if (ret.kind === 'none' || ret.kind === 'error') {
|
|
187
226
|
// The central IdP had no session (or the bounce failed). Record it so we do
|
|
188
227
|
// not bounce again this tab — the definitive loop breaker.
|
|
189
228
|
markNoSession();
|
|
190
|
-
|
|
229
|
+
leaveCallbackHard();
|
|
191
230
|
return null;
|
|
192
231
|
}
|
|
193
232
|
if (!stateOk || !ret.code) {
|
|
194
233
|
// Forged / replayed / stale fragment, or a malformed ok with no code. Treat
|
|
195
234
|
// exactly like "no session": never exchange, never loop.
|
|
196
235
|
markNoSession();
|
|
197
|
-
|
|
236
|
+
leaveCallbackHard();
|
|
198
237
|
return null;
|
|
199
238
|
}
|
|
200
239
|
let session;
|
|
@@ -204,14 +243,22 @@ async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
204
243
|
catch (error) {
|
|
205
244
|
onExchangeError?.(error);
|
|
206
245
|
markNoSession();
|
|
207
|
-
|
|
246
|
+
leaveCallbackHard();
|
|
208
247
|
return null;
|
|
209
248
|
}
|
|
210
249
|
if (!session?.sessionId) {
|
|
211
250
|
markNoSession();
|
|
212
|
-
|
|
251
|
+
leaveCallbackHard();
|
|
213
252
|
return null;
|
|
214
253
|
}
|
|
215
|
-
|
|
254
|
+
// `ok`: the provider is about to commit the freshly exchanged in-memory
|
|
255
|
+
// session — do NOT hard-redirect (a full navigation would discard it). Use a
|
|
256
|
+
// SOFT `history.replaceState` to the target + a synthetic `popstate` so
|
|
257
|
+
// URL-driven routers re-sync to the restored route without a reload.
|
|
258
|
+
const target = consumeCallbackTarget();
|
|
259
|
+
if (target !== null) {
|
|
260
|
+
history.replaceState(null, '', target);
|
|
261
|
+
dispatchPopState();
|
|
262
|
+
}
|
|
216
263
|
return session;
|
|
217
264
|
}
|