@oxyhq/core 3.0.0 → 3.2.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 +11 -2
- package/dist/cjs/OxyServices.js +1 -0
- package/dist/cjs/mixins/OxyServices.applications.js +33 -3
- package/dist/cjs/mixins/OxyServices.utility.js +3 -0
- package/dist/cjs/mixins/OxyServices.workspaces.js +144 -0
- package/dist/cjs/mixins/index.js +2 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +11 -2
- package/dist/esm/OxyServices.js +1 -0
- package/dist/esm/mixins/OxyServices.applications.js +33 -3
- package/dist/esm/mixins/OxyServices.utility.js +3 -0
- package/dist/esm/mixins/OxyServices.workspaces.js +141 -0
- package/dist/esm/mixins/index.js +2 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.d.ts +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +99 -5
- package/dist/types/mixins/OxyServices.utility.d.ts +7 -0
- package/dist/types/mixins/OxyServices.workspaces.d.ts +199 -0
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/interfaces.d.ts +9 -0
- package/package.json +1 -1
- package/src/AuthManager.ts +11 -2
- package/src/OxyServices.ts +1 -0
- package/src/__tests__/authManager.cookiePath.test.ts +49 -0
- package/src/index.ts +20 -0
- package/src/mixins/OxyServices.applications.ts +119 -7
- package/src/mixins/OxyServices.utility.ts +11 -0
- package/src/mixins/OxyServices.workspaces.ts +309 -0
- package/src/mixins/index.ts +3 -0
- package/src/models/interfaces.ts +9 -0
|
@@ -58,12 +58,22 @@ export interface Application {
|
|
|
58
58
|
webhookUrl?: string;
|
|
59
59
|
devWebhookUrl?: string;
|
|
60
60
|
createdByUserId: string;
|
|
61
|
+
/**
|
|
62
|
+
* The workspace this application belongs to (workspace `_id`), or `null` for
|
|
63
|
+
* applications not owned by a workspace. Used by the console to scope apps to
|
|
64
|
+
* a workspace and to branch on workspace-derived access.
|
|
65
|
+
*/
|
|
66
|
+
workspaceId: string | null;
|
|
61
67
|
createdAt: string;
|
|
62
68
|
updatedAt: string;
|
|
63
69
|
/**
|
|
64
70
|
* The calling user's own membership in this application, embedded by the API
|
|
65
71
|
* on list (`GET /applications`) and detail (`GET /applications/:appId`)
|
|
66
72
|
* responses. Use `callerMembership.permissions` to gate UI affordances.
|
|
73
|
+
*
|
|
74
|
+
* When the caller's access is derived from a workspace membership rather than
|
|
75
|
+
* a direct application membership, the API returns a synthetic membership
|
|
76
|
+
* with `source: 'workspace'` and `_id: null`.
|
|
67
77
|
*/
|
|
68
78
|
callerMembership?: ApplicationMember;
|
|
69
79
|
}
|
|
@@ -73,7 +83,11 @@ export interface Application {
|
|
|
73
83
|
* on the server at write time.
|
|
74
84
|
*/
|
|
75
85
|
export interface ApplicationMember {
|
|
76
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The membership's Mongo `_id`. `null` for a synthetic, workspace-derived
|
|
88
|
+
* membership (see {@link Application.callerMembership} and `source`).
|
|
89
|
+
*/
|
|
90
|
+
_id: string | null;
|
|
77
91
|
applicationId: string;
|
|
78
92
|
userId: string;
|
|
79
93
|
role: ApplicationRole;
|
|
@@ -81,6 +95,13 @@ export interface ApplicationMember {
|
|
|
81
95
|
invitedByUserId?: string;
|
|
82
96
|
joinedAt?: string;
|
|
83
97
|
status: ApplicationMemberStatus;
|
|
98
|
+
/**
|
|
99
|
+
* Origin of this membership. When `'workspace'`, the membership is synthetic
|
|
100
|
+
* and derived from the caller's workspace membership rather than a direct
|
|
101
|
+
* application membership (in which case `_id` is `null`). Absent or any other
|
|
102
|
+
* value indicates a direct application membership.
|
|
103
|
+
*/
|
|
104
|
+
source?: 'workspace';
|
|
84
105
|
createdAt: string;
|
|
85
106
|
updatedAt: string;
|
|
86
107
|
}
|
|
@@ -100,11 +121,49 @@ export interface ApplicationCredential {
|
|
|
100
121
|
status: ApplicationCredentialStatus;
|
|
101
122
|
lastUsedAt?: string;
|
|
102
123
|
expiresAt?: string;
|
|
124
|
+
/**
|
|
125
|
+
* Audit link to the credential this one was rotated FROM. Populated by the
|
|
126
|
+
* API on credentials created via rotation; absent on original credentials.
|
|
127
|
+
*/
|
|
128
|
+
rotatedFromCredentialId?: string;
|
|
103
129
|
createdByUserId: string;
|
|
104
130
|
createdAt: string;
|
|
105
131
|
updatedAt: string;
|
|
106
132
|
}
|
|
107
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Sanitized, PUBLIC application identity returned by the API when resolving a
|
|
136
|
+
* cross-app/OAuth client to a registered {@link Application}.
|
|
137
|
+
*
|
|
138
|
+
* Unlike {@link Application}, this shape carries NO sensitive or membership
|
|
139
|
+
* fields — it is safe to display unauthenticated in consent/authorize screens
|
|
140
|
+
* and device-flow approval UIs. The API resolves a `client_id` (OAuth
|
|
141
|
+
* credential public key) to the owning application and projects only the
|
|
142
|
+
* fields below. `id` is the application's `_id` as a string.
|
|
143
|
+
*/
|
|
144
|
+
export interface PublicApplication {
|
|
145
|
+
/** The application's Mongo `_id` as a string. */
|
|
146
|
+
id: string;
|
|
147
|
+
/** Human-readable application name shown to the user. */
|
|
148
|
+
name: string;
|
|
149
|
+
/** Optional short description of what the application does. */
|
|
150
|
+
description?: string;
|
|
151
|
+
/** Optional icon URL for the application. */
|
|
152
|
+
icon?: string;
|
|
153
|
+
/** Optional public website/homepage URL for the application. */
|
|
154
|
+
websiteUrl?: string;
|
|
155
|
+
/** Application classification (set by Oxy platform staff). */
|
|
156
|
+
type: ApplicationType;
|
|
157
|
+
/** Whether the application is an officially endorsed Oxy application. */
|
|
158
|
+
isOfficial: boolean;
|
|
159
|
+
/** Whether the application is an internal Oxy ecosystem application. */
|
|
160
|
+
isInternal: boolean;
|
|
161
|
+
/** OAuth scopes the application is configured to request. */
|
|
162
|
+
scopes: string[];
|
|
163
|
+
/** Optional display name of the developer/owner organisation. */
|
|
164
|
+
developerName?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
108
167
|
/** Input accepted by `createApplication`. Staff-only fields are not settable here. */
|
|
109
168
|
export interface CreateApplicationInput {
|
|
110
169
|
name: string;
|
|
@@ -113,6 +172,11 @@ export interface CreateApplicationInput {
|
|
|
113
172
|
icon?: string;
|
|
114
173
|
redirectUris?: string[];
|
|
115
174
|
scopes?: string[];
|
|
175
|
+
/**
|
|
176
|
+
* Optional workspace `_id` to create the app in. Omitted → API defaults to
|
|
177
|
+
* the caller's personal workspace.
|
|
178
|
+
*/
|
|
179
|
+
workspaceId?: string;
|
|
116
180
|
}
|
|
117
181
|
|
|
118
182
|
/** Input accepted by `updateApplication`. Staff-only fields are not settable here. */
|
|
@@ -152,12 +216,25 @@ export interface CreateApplicationCredentialInput {
|
|
|
152
216
|
scopes?: string[];
|
|
153
217
|
}
|
|
154
218
|
|
|
155
|
-
/** Result of creating
|
|
219
|
+
/** Result of creating a credential — `secret` is returned ONCE. */
|
|
156
220
|
export interface ApplicationCredentialWithSecret {
|
|
157
221
|
credential: ApplicationCredential;
|
|
158
222
|
secret: string;
|
|
159
223
|
}
|
|
160
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Result of rotating a credential. Extends the create result with audit fields:
|
|
227
|
+
* the new plaintext `secret` is returned ONCE, plus `rotatedFrom` (the previous
|
|
228
|
+
* credential's `credentialId`) and `graceExpiresAt` (ISO string marking when the
|
|
229
|
+
* old credential stops being honoured during the rotation grace window).
|
|
230
|
+
*/
|
|
231
|
+
export interface RotateApplicationCredentialResult extends ApplicationCredentialWithSecret {
|
|
232
|
+
/** The previous credential's `credentialId` that this rotation supersedes. */
|
|
233
|
+
rotatedFrom: string;
|
|
234
|
+
/** ISO timestamp at which the rotated-from credential's grace window ends. */
|
|
235
|
+
graceExpiresAt: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
161
238
|
/** Time window for application usage statistics. */
|
|
162
239
|
export type ApplicationUsagePeriod = '24h' | '7d' | '30d' | '90d';
|
|
163
240
|
|
|
@@ -204,14 +281,47 @@ export function OxyServicesApplicationsMixin<T extends typeof OxyServicesBase>(B
|
|
|
204
281
|
super(...(args as [any]));
|
|
205
282
|
}
|
|
206
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Resolve an OAuth client identifier to the owning application's PUBLIC
|
|
286
|
+
* identity. No authentication required — the API returns only sanitized,
|
|
287
|
+
* display-safe metadata ({@link PublicApplication}). Use this to render the
|
|
288
|
+
* requesting application's name/icon in consent, authorize, and device-flow
|
|
289
|
+
* approval UIs before any session exists.
|
|
290
|
+
*
|
|
291
|
+
* @param clientId - The OAuth `client_id` (an active credential's public
|
|
292
|
+
* key). URL-encoded before being placed in the path.
|
|
293
|
+
*/
|
|
294
|
+
async getPublicApplication(clientId: string): Promise<PublicApplication> {
|
|
295
|
+
try {
|
|
296
|
+
const res = await this.makeRequest<{ application: PublicApplication }>(
|
|
297
|
+
'GET',
|
|
298
|
+
`/auth/oauth/client/${encodeURIComponent(clientId)}`,
|
|
299
|
+
undefined,
|
|
300
|
+
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
301
|
+
);
|
|
302
|
+
return res.application;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw this.handleError(error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
207
308
|
/**
|
|
208
309
|
* List applications the current user is an active member of.
|
|
310
|
+
*
|
|
311
|
+
* @param workspaceId - Optional workspace `_id` to scope the listing to
|
|
312
|
+
* applications belonging to that workspace. When provided it is appended
|
|
313
|
+
* as a `workspaceId` query parameter (URL-encoded). The query string is
|
|
314
|
+
* part of the request path, so the response cache keys on it
|
|
315
|
+
* automatically — scoped and unscoped lists never collide.
|
|
209
316
|
*/
|
|
210
|
-
async getApplications(): Promise<Application[]> {
|
|
317
|
+
async getApplications(workspaceId?: string): Promise<Application[]> {
|
|
211
318
|
try {
|
|
319
|
+
const path = workspaceId
|
|
320
|
+
? `/applications?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
321
|
+
: '/applications';
|
|
212
322
|
const res = await this.makeRequest<{ applications?: Application[] }>(
|
|
213
323
|
'GET',
|
|
214
|
-
|
|
324
|
+
path,
|
|
215
325
|
undefined,
|
|
216
326
|
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
217
327
|
);
|
|
@@ -445,16 +555,18 @@ export function OxyServicesApplicationsMixin<T extends typeof OxyServicesBase>(B
|
|
|
445
555
|
|
|
446
556
|
/**
|
|
447
557
|
* Rotate a credential's secret. The new plaintext `secret` is returned
|
|
448
|
-
* exactly ONCE
|
|
558
|
+
* exactly ONCE, along with audit fields: `rotatedFrom` (the previous
|
|
559
|
+
* credentialId) and `graceExpiresAt` (ISO string for the grace window during
|
|
560
|
+
* which the old credential is still honoured).
|
|
449
561
|
* @param applicationId - The application's Mongo `_id`.
|
|
450
562
|
* @param credentialId - The credential's Mongo `_id`.
|
|
451
563
|
*/
|
|
452
564
|
async rotateApplicationCredential(
|
|
453
565
|
applicationId: string,
|
|
454
566
|
credentialId: string,
|
|
455
|
-
): Promise<
|
|
567
|
+
): Promise<RotateApplicationCredentialResult> {
|
|
456
568
|
try {
|
|
457
|
-
return await this.makeRequest<
|
|
569
|
+
return await this.makeRequest<RotateApplicationCredentialResult>(
|
|
458
570
|
'POST',
|
|
459
571
|
`/applications/${applicationId}/credentials/${credentialId}/rotate`,
|
|
460
572
|
undefined,
|
|
@@ -18,6 +18,7 @@ interface JwtPayload {
|
|
|
18
18
|
sessionId?: string;
|
|
19
19
|
type?: string;
|
|
20
20
|
appId?: string;
|
|
21
|
+
credentialId?: string;
|
|
21
22
|
appName?: string;
|
|
22
23
|
scopes?: string[];
|
|
23
24
|
aud?: string | string[];
|
|
@@ -61,6 +62,13 @@ export interface ServiceApp {
|
|
|
61
62
|
appId: string;
|
|
62
63
|
appName: string;
|
|
63
64
|
scopes: string[];
|
|
65
|
+
/**
|
|
66
|
+
* The credentialId of the specific service credential that minted this token.
|
|
67
|
+
* Carried by newer service-token JWTs alongside `appId`; absent on tokens
|
|
68
|
+
* issued before credential-level audit linking. Use for per-credential audit
|
|
69
|
+
* trails and rotation alignment (GitHub #215).
|
|
70
|
+
*/
|
|
71
|
+
credentialId?: string;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
/**
|
|
@@ -618,6 +626,9 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
618
626
|
appId,
|
|
619
627
|
appName: decoded.appName || 'unknown',
|
|
620
628
|
scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
|
|
629
|
+
...(typeof decoded.credentialId === 'string' && decoded.credentialId.length > 0
|
|
630
|
+
? { credentialId: decoded.credentialId }
|
|
631
|
+
: {}),
|
|
621
632
|
};
|
|
622
633
|
|
|
623
634
|
if (debug) {
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspaces Methods Mixin
|
|
3
|
+
*
|
|
4
|
+
* Provides methods for managing Oxy workspaces and their members via the
|
|
5
|
+
* `/workspaces` API. A workspace is a multi-user container that owns
|
|
6
|
+
* applications and other resources: membership (with a role) grants
|
|
7
|
+
* permissions. A `personal` workspace is created implicitly for every user;
|
|
8
|
+
* `team` workspaces are created explicitly and can invite additional members.
|
|
9
|
+
*
|
|
10
|
+
* Reference workspaces by their Mongo `_id` and members by their member `_id`.
|
|
11
|
+
* Never by name or slug.
|
|
12
|
+
*/
|
|
13
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
14
|
+
import { CACHE_TIMES } from './mixinHelpers';
|
|
15
|
+
|
|
16
|
+
/** Role a member holds within a workspace. */
|
|
17
|
+
export type WorkspaceRole = 'owner' | 'admin' | 'member' | 'viewer';
|
|
18
|
+
|
|
19
|
+
/** Workspace classification. A `personal` workspace is implicit per user. */
|
|
20
|
+
export type WorkspaceType = 'personal' | 'team';
|
|
21
|
+
|
|
22
|
+
/** Lifecycle status of a workspace. */
|
|
23
|
+
export type WorkspaceStatus = 'active' | 'deleted';
|
|
24
|
+
|
|
25
|
+
/** Membership lifecycle status. */
|
|
26
|
+
export type WorkspaceMemberStatus = 'active' | 'invited' | 'removed';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Client-facing WorkspaceMember shape. `permissions` is derived from `role`
|
|
30
|
+
* on the server at write time.
|
|
31
|
+
*/
|
|
32
|
+
export interface WorkspaceMember {
|
|
33
|
+
_id: string;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
userId: string;
|
|
36
|
+
role: WorkspaceRole;
|
|
37
|
+
permissions: string[];
|
|
38
|
+
invitedByUserId?: string | null;
|
|
39
|
+
joinedAt?: string | null;
|
|
40
|
+
status: WorkspaceMemberStatus;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
updatedAt: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Client-facing Workspace shape returned by the `/workspaces` API. Mirrors the
|
|
47
|
+
* server `Workspace` model with `_id` as a string and dates serialized to ISO
|
|
48
|
+
* strings.
|
|
49
|
+
*/
|
|
50
|
+
export interface Workspace {
|
|
51
|
+
_id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
slug: string;
|
|
54
|
+
type: WorkspaceType;
|
|
55
|
+
description?: string | null;
|
|
56
|
+
icon?: string | null;
|
|
57
|
+
ownerId: string;
|
|
58
|
+
status: WorkspaceStatus;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
/**
|
|
62
|
+
* The calling user's own membership in this workspace, embedded by the API
|
|
63
|
+
* on list (`GET /workspaces`) and detail (`GET /workspaces/:id`) responses.
|
|
64
|
+
* Use `callerMembership.permissions` to gate UI affordances.
|
|
65
|
+
*/
|
|
66
|
+
callerMembership?: WorkspaceMember | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Input accepted by `createWorkspace`. */
|
|
70
|
+
export interface CreateWorkspaceInput {
|
|
71
|
+
name: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
icon?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Input accepted by `updateWorkspace`. */
|
|
77
|
+
export interface UpdateWorkspaceInput {
|
|
78
|
+
name?: string;
|
|
79
|
+
description?: string | null;
|
|
80
|
+
icon?: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Input accepted by `inviteWorkspaceMember`. The owner role cannot be invited. */
|
|
84
|
+
export interface InviteWorkspaceMemberInput {
|
|
85
|
+
userId: string;
|
|
86
|
+
role: Exclude<WorkspaceRole, 'owner'>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Input accepted by `updateWorkspaceMember`. The owner role cannot be assigned. */
|
|
90
|
+
export interface UpdateWorkspaceMemberInput {
|
|
91
|
+
role: Exclude<WorkspaceRole, 'owner'>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Input accepted by `transferWorkspaceOwnership`. */
|
|
95
|
+
export interface TransferWorkspaceOwnershipInput {
|
|
96
|
+
userId: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Result of a delete/remove/transfer operation. */
|
|
100
|
+
export interface WorkspaceSuccessResult {
|
|
101
|
+
success: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function OxyServicesWorkspacesMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
105
|
+
return class extends Base {
|
|
106
|
+
constructor(...args: any[]) {
|
|
107
|
+
super(...(args as [any]));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* List workspaces the current user is an active member of.
|
|
112
|
+
*/
|
|
113
|
+
async getWorkspaces(): Promise<Workspace[]> {
|
|
114
|
+
try {
|
|
115
|
+
const res = await this.makeRequest<{ workspaces?: Workspace[] }>(
|
|
116
|
+
'GET',
|
|
117
|
+
'/workspaces',
|
|
118
|
+
undefined,
|
|
119
|
+
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
120
|
+
);
|
|
121
|
+
return res.workspaces ?? [];
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw this.handleError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a new team workspace. The caller becomes its `owner`.
|
|
129
|
+
* @param data - Workspace configuration.
|
|
130
|
+
*/
|
|
131
|
+
async createWorkspace(data: CreateWorkspaceInput): Promise<Workspace> {
|
|
132
|
+
try {
|
|
133
|
+
const res = await this.makeRequest<{ workspace: Workspace }>(
|
|
134
|
+
'POST',
|
|
135
|
+
'/workspaces',
|
|
136
|
+
data,
|
|
137
|
+
{ cache: false },
|
|
138
|
+
);
|
|
139
|
+
return res.workspace;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw this.handleError(error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Fetch a single workspace by id.
|
|
147
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
148
|
+
*/
|
|
149
|
+
async getWorkspace(workspaceId: string): Promise<Workspace> {
|
|
150
|
+
try {
|
|
151
|
+
const res = await this.makeRequest<{ workspace: Workspace }>(
|
|
152
|
+
'GET',
|
|
153
|
+
`/workspaces/${encodeURIComponent(workspaceId)}`,
|
|
154
|
+
undefined,
|
|
155
|
+
{ cache: true, cacheTTL: CACHE_TIMES.LONG },
|
|
156
|
+
);
|
|
157
|
+
return res.workspace;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw this.handleError(error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update a workspace's mutable fields.
|
|
165
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
166
|
+
* @param data - Subset of updatable fields.
|
|
167
|
+
*/
|
|
168
|
+
async updateWorkspace(
|
|
169
|
+
workspaceId: string,
|
|
170
|
+
data: UpdateWorkspaceInput,
|
|
171
|
+
): Promise<Workspace> {
|
|
172
|
+
try {
|
|
173
|
+
const res = await this.makeRequest<{ workspace: Workspace }>(
|
|
174
|
+
'PATCH',
|
|
175
|
+
`/workspaces/${encodeURIComponent(workspaceId)}`,
|
|
176
|
+
data,
|
|
177
|
+
{ cache: false },
|
|
178
|
+
);
|
|
179
|
+
return res.workspace;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw this.handleError(error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Soft-delete a workspace (owner only).
|
|
187
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
188
|
+
*/
|
|
189
|
+
async deleteWorkspace(workspaceId: string): Promise<WorkspaceSuccessResult> {
|
|
190
|
+
try {
|
|
191
|
+
return await this.makeRequest<WorkspaceSuccessResult>(
|
|
192
|
+
'DELETE',
|
|
193
|
+
`/workspaces/${encodeURIComponent(workspaceId)}`,
|
|
194
|
+
undefined,
|
|
195
|
+
{ cache: false },
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw this.handleError(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* List members of a workspace.
|
|
204
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
205
|
+
*/
|
|
206
|
+
async getWorkspaceMembers(workspaceId: string): Promise<WorkspaceMember[]> {
|
|
207
|
+
try {
|
|
208
|
+
const res = await this.makeRequest<{ members?: WorkspaceMember[] }>(
|
|
209
|
+
'GET',
|
|
210
|
+
`/workspaces/${encodeURIComponent(workspaceId)}/members`,
|
|
211
|
+
undefined,
|
|
212
|
+
{ cache: true, cacheTTL: CACHE_TIMES.MEDIUM },
|
|
213
|
+
);
|
|
214
|
+
return res.members ?? [];
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw this.handleError(error);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Add a member to a workspace.
|
|
222
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
223
|
+
* @param data - Target user id and role (never `owner`).
|
|
224
|
+
*/
|
|
225
|
+
async inviteWorkspaceMember(
|
|
226
|
+
workspaceId: string,
|
|
227
|
+
data: InviteWorkspaceMemberInput,
|
|
228
|
+
): Promise<WorkspaceMember> {
|
|
229
|
+
try {
|
|
230
|
+
const res = await this.makeRequest<{ member: WorkspaceMember }>(
|
|
231
|
+
'POST',
|
|
232
|
+
`/workspaces/${encodeURIComponent(workspaceId)}/members`,
|
|
233
|
+
data,
|
|
234
|
+
{ cache: false },
|
|
235
|
+
);
|
|
236
|
+
return res.member;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw this.handleError(error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Change a member's role.
|
|
244
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
245
|
+
* @param memberId - The member's Mongo `_id`.
|
|
246
|
+
* @param data - New role (never `owner`).
|
|
247
|
+
*/
|
|
248
|
+
async updateWorkspaceMember(
|
|
249
|
+
workspaceId: string,
|
|
250
|
+
memberId: string,
|
|
251
|
+
data: UpdateWorkspaceMemberInput,
|
|
252
|
+
): Promise<WorkspaceMember> {
|
|
253
|
+
try {
|
|
254
|
+
const res = await this.makeRequest<{ member: WorkspaceMember }>(
|
|
255
|
+
'PATCH',
|
|
256
|
+
`/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`,
|
|
257
|
+
data,
|
|
258
|
+
{ cache: false },
|
|
259
|
+
);
|
|
260
|
+
return res.member;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
throw this.handleError(error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Remove a member from a workspace.
|
|
268
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
269
|
+
* @param memberId - The member's Mongo `_id`.
|
|
270
|
+
*/
|
|
271
|
+
async removeWorkspaceMember(
|
|
272
|
+
workspaceId: string,
|
|
273
|
+
memberId: string,
|
|
274
|
+
): Promise<WorkspaceSuccessResult> {
|
|
275
|
+
try {
|
|
276
|
+
return await this.makeRequest<WorkspaceSuccessResult>(
|
|
277
|
+
'DELETE',
|
|
278
|
+
`/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`,
|
|
279
|
+
undefined,
|
|
280
|
+
{ cache: false },
|
|
281
|
+
);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw this.handleError(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Transfer ownership of a workspace to another member (owner only).
|
|
289
|
+
* Demotes the current owner and promotes the target to `owner`.
|
|
290
|
+
* @param workspaceId - The workspace's Mongo `_id`.
|
|
291
|
+
* @param data - Target user id.
|
|
292
|
+
*/
|
|
293
|
+
async transferWorkspaceOwnership(
|
|
294
|
+
workspaceId: string,
|
|
295
|
+
data: TransferWorkspaceOwnershipInput,
|
|
296
|
+
): Promise<WorkspaceSuccessResult> {
|
|
297
|
+
try {
|
|
298
|
+
return await this.makeRequest<WorkspaceSuccessResult>(
|
|
299
|
+
'POST',
|
|
300
|
+
`/workspaces/${encodeURIComponent(workspaceId)}/transfer-ownership`,
|
|
301
|
+
data,
|
|
302
|
+
{ cache: false },
|
|
303
|
+
);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
throw this.handleError(error);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
package/src/mixins/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { OxyServicesPaymentMixin } from './OxyServices.payment';
|
|
|
18
18
|
import { OxyServicesKarmaMixin } from './OxyServices.karma';
|
|
19
19
|
import { OxyServicesAssetsMixin } from './OxyServices.assets';
|
|
20
20
|
import { OxyServicesApplicationsMixin } from './OxyServices.applications';
|
|
21
|
+
import { OxyServicesWorkspacesMixin } from './OxyServices.workspaces';
|
|
21
22
|
import { OxyServicesLocationMixin } from './OxyServices.location';
|
|
22
23
|
import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
|
|
23
24
|
import { OxyServicesDevicesMixin } from './OxyServices.devices';
|
|
@@ -51,6 +52,7 @@ type AllMixinInstances =
|
|
|
51
52
|
& InstanceType<ReturnType<typeof OxyServicesKarmaMixin<typeof OxyServicesBase>>>
|
|
52
53
|
& InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>>
|
|
53
54
|
& InstanceType<ReturnType<typeof OxyServicesApplicationsMixin<typeof OxyServicesBase>>>
|
|
55
|
+
& InstanceType<ReturnType<typeof OxyServicesWorkspacesMixin<typeof OxyServicesBase>>>
|
|
54
56
|
& InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>>
|
|
55
57
|
& InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>>
|
|
56
58
|
& InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>>
|
|
@@ -115,6 +117,7 @@ const MIXIN_PIPELINE: MixinFunction[] = [
|
|
|
115
117
|
OxyServicesKarmaMixin,
|
|
116
118
|
OxyServicesAssetsMixin,
|
|
117
119
|
OxyServicesApplicationsMixin,
|
|
120
|
+
OxyServicesWorkspacesMixin,
|
|
118
121
|
OxyServicesLocationMixin,
|
|
119
122
|
OxyServicesAnalyticsMixin,
|
|
120
123
|
OxyServicesDevicesMixin,
|
package/src/models/interfaces.ts
CHANGED
|
@@ -17,6 +17,15 @@ export interface OxyConfig {
|
|
|
17
17
|
sessionBaseUrl?: string;
|
|
18
18
|
authWebUrl?: string;
|
|
19
19
|
authRedirectUri?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The app's Oxy OAuth client id (ApplicationCredential publicKey).
|
|
22
|
+
*
|
|
23
|
+
* Identifies this app in OAuth authorize / consent flows (issue #214). Purely
|
|
24
|
+
* declarative: the SDK stores it on `OxyServices.config.clientId` for later
|
|
25
|
+
* OAuth-authorize use. It is unrelated to the cross-domain `/sso?client_id=…`
|
|
26
|
+
* bounce (which uses the RP origin, not this registered client id).
|
|
27
|
+
*/
|
|
28
|
+
clientId?: string;
|
|
20
29
|
// Performance & caching options
|
|
21
30
|
enableCache?: boolean;
|
|
22
31
|
cacheTTL?: number; // Cache TTL in milliseconds (default: 5 minutes)
|