@unboundcx/sdk 4.13.110 → 4.13.112-security.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/base.js +65 -3
- package/index.js +3 -0
- package/lib/refreshInterceptor.js +106 -0
- package/package.json +2 -1
- package/services/licenses.js +330 -0
- package/services/login.js +21 -0
- package/services/objects.js +0 -40
- package/services/workflows.js +43 -96
- package/services/workflowTools.js +0 -74
package/base.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* npm install mime-types
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { handleUnauthorized } from './lib/refreshInterceptor.js';
|
|
17
|
+
|
|
16
18
|
const requestBySdk = new WeakMap();
|
|
17
19
|
const SDK_REQUEST = Symbol.for('unbound.sdk.request');
|
|
18
20
|
|
|
@@ -49,12 +51,33 @@ export class BaseSDK {
|
|
|
49
51
|
this.fwRequestId = arguments[3];
|
|
50
52
|
} else {
|
|
51
53
|
// New object-based parameters
|
|
52
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
namespace,
|
|
56
|
+
callId,
|
|
57
|
+
token,
|
|
58
|
+
fwRequestId,
|
|
59
|
+
baseURL,
|
|
60
|
+
autoRefresh,
|
|
61
|
+
onUnauthorized,
|
|
62
|
+
refreshToken,
|
|
63
|
+
} = options;
|
|
53
64
|
this.namespace = namespace || process?.env?.namespace;
|
|
54
65
|
this.callId = callId;
|
|
55
66
|
this.token = token;
|
|
56
67
|
this.fwRequestId = fwRequestId;
|
|
57
68
|
this._constructorBaseURL = baseURL;
|
|
69
|
+
// api#222 -- opt-in only (default false): existing callers get no
|
|
70
|
+
// behavior change. When on, a 401 from any non-/login* endpoint
|
|
71
|
+
// triggers one single-flight refresh + one retry (lib/refreshInterceptor.js).
|
|
72
|
+
this._autoRefresh = autoRefresh === true;
|
|
73
|
+
this._onUnauthorized =
|
|
74
|
+
typeof onUnauthorized === 'function' ? onUnauthorized : null;
|
|
75
|
+
// Bearer-delivery clients only: the raw refresh token, so autoRefresh
|
|
76
|
+
// has something to send to POST /login/refresh (cookie clients rely
|
|
77
|
+
// on the browser's own refreshToken cookie and never need this).
|
|
78
|
+
// Seeded via this option or setRefreshToken(); updated in place on
|
|
79
|
+
// every rotation by lib/refreshInterceptor.js.
|
|
80
|
+
this._refreshToken = refreshToken;
|
|
58
81
|
}
|
|
59
82
|
this.baseURL;
|
|
60
83
|
this.transports = new Map();
|
|
@@ -104,6 +127,10 @@ export class BaseSDK {
|
|
|
104
127
|
this.token = token;
|
|
105
128
|
}
|
|
106
129
|
|
|
130
|
+
setRefreshToken(refreshToken) {
|
|
131
|
+
this._refreshToken = refreshToken;
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
setNamespace(namespace) {
|
|
108
135
|
this.namespace = namespace;
|
|
109
136
|
const defaultDomain = 'api.unbound.cx';
|
|
@@ -278,6 +305,7 @@ export class BaseSDK {
|
|
|
278
305
|
params,
|
|
279
306
|
returnRawResponse,
|
|
280
307
|
startTime,
|
|
308
|
+
forceFetch,
|
|
281
309
|
);
|
|
282
310
|
}
|
|
283
311
|
} else {
|
|
@@ -292,6 +320,7 @@ export class BaseSDK {
|
|
|
292
320
|
params,
|
|
293
321
|
returnRawResponse,
|
|
294
322
|
startTime,
|
|
323
|
+
forceFetch,
|
|
295
324
|
);
|
|
296
325
|
}
|
|
297
326
|
|
|
@@ -307,6 +336,7 @@ export class BaseSDK {
|
|
|
307
336
|
method,
|
|
308
337
|
endpoint,
|
|
309
338
|
duration,
|
|
339
|
+
{ originalParams: params, forceFetch },
|
|
310
340
|
);
|
|
311
341
|
}
|
|
312
342
|
|
|
@@ -357,6 +387,7 @@ export class BaseSDK {
|
|
|
357
387
|
params = {},
|
|
358
388
|
returnRawResponse = false,
|
|
359
389
|
startTime = Date.now(),
|
|
390
|
+
forceFetch = false,
|
|
360
391
|
) {
|
|
361
392
|
const { body, query, headers = {} } = params;
|
|
362
393
|
|
|
@@ -438,10 +469,20 @@ export class BaseSDK {
|
|
|
438
469
|
return response;
|
|
439
470
|
}
|
|
440
471
|
|
|
441
|
-
return this._processResponse(response, 'https', method, endpoint, duration
|
|
472
|
+
return this._processResponse(response, 'https', method, endpoint, duration, {
|
|
473
|
+
originalParams: params,
|
|
474
|
+
forceFetch,
|
|
475
|
+
});
|
|
442
476
|
}
|
|
443
477
|
|
|
444
|
-
async _processResponse(
|
|
478
|
+
async _processResponse(
|
|
479
|
+
response,
|
|
480
|
+
transport,
|
|
481
|
+
method,
|
|
482
|
+
endpoint,
|
|
483
|
+
duration = 0,
|
|
484
|
+
retryCtx = {},
|
|
485
|
+
) {
|
|
445
486
|
// Check if the response indicates an HTTP error
|
|
446
487
|
// These are API/configuration errors, not transport failures
|
|
447
488
|
|
|
@@ -518,6 +559,27 @@ export class BaseSDK {
|
|
|
518
559
|
);
|
|
519
560
|
}
|
|
520
561
|
|
|
562
|
+
// api#222 -- opt-in single-flight refresh + one retry on 401. Off by
|
|
563
|
+
// default (this._autoRefresh set only via the `autoRefresh` ctor
|
|
564
|
+
// option); the retry's own params carry `__skipAutoRefresh` so a 401
|
|
565
|
+
// on the RETRY itself always falls through to onUnauthorized instead
|
|
566
|
+
// of looping.
|
|
567
|
+
if (response.status === 401 && this._autoRefresh) {
|
|
568
|
+
return handleUnauthorized(this, {
|
|
569
|
+
status: response.status,
|
|
570
|
+
endpoint,
|
|
571
|
+
alreadyRetried: retryCtx?.originalParams?.__skipAutoRefresh === true,
|
|
572
|
+
originalError: httpError,
|
|
573
|
+
retry: () =>
|
|
574
|
+
this.#request(
|
|
575
|
+
endpoint,
|
|
576
|
+
method,
|
|
577
|
+
{ ...(retryCtx.originalParams || {}), __skipAutoRefresh: true },
|
|
578
|
+
retryCtx.forceFetch,
|
|
579
|
+
),
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
521
583
|
throw httpError;
|
|
522
584
|
}
|
|
523
585
|
|
package/index.js
CHANGED
|
@@ -38,6 +38,7 @@ import { FaxService } from './services/fax.js';
|
|
|
38
38
|
import { DocumentsService } from './services/documents.js';
|
|
39
39
|
import { EsignService } from './services/esign.js';
|
|
40
40
|
import { PermissionsService } from './services/permissions.js';
|
|
41
|
+
import { LicensesService } from './services/licenses.js';
|
|
41
42
|
import { UsersService } from './services/users.js';
|
|
42
43
|
import { TriggersService } from './services/triggers.js';
|
|
43
44
|
import { RecentsService } from './services/recents.js';
|
|
@@ -138,6 +139,7 @@ class UnboundSDK extends BaseSDK {
|
|
|
138
139
|
this.documents = new DocumentsService(this);
|
|
139
140
|
this.esign = new EsignService(this);
|
|
140
141
|
this.permissions = new PermissionsService(this);
|
|
142
|
+
this.licenses = new LicensesService(this);
|
|
141
143
|
this.users = new UsersService(this);
|
|
142
144
|
this.reporting = new ReportingService(this);
|
|
143
145
|
this.triggers = new TriggersService(this);
|
|
@@ -344,6 +346,7 @@ export { KnowledgeBaseService } from './services/knowledgeBase.js';
|
|
|
344
346
|
export { FaxService } from './services/fax.js';
|
|
345
347
|
export { EsignService, EsignPublicService } from './services/esign.js';
|
|
346
348
|
export { PermissionsService } from './services/permissions.js';
|
|
349
|
+
export { LicensesService } from './services/licenses.js';
|
|
347
350
|
export { UsersService } from './services/users.js';
|
|
348
351
|
export { ReportingService } from './services/reporting.js';
|
|
349
352
|
export { RecentsService } from './services/recents.js';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// api#222 client contract -- single-flight 401 -> refresh -> retry-once.
|
|
2
|
+
// base.js's `#request` funnels BOTH the Socket.IO transport and the plain
|
|
3
|
+
// HTTP fallback through the same `_processResponse` (see base.js), so this
|
|
4
|
+
// one hook covers every transport without a per-transport copy.
|
|
5
|
+
//
|
|
6
|
+
// Usage (base.js only -- app code never imports this directly):
|
|
7
|
+
// handleUnauthorized(sdk, { status, endpoint, retry, originalError })
|
|
8
|
+
//
|
|
9
|
+
// `retry` is a caller-supplied () => Promise that re-issues the ORIGINAL
|
|
10
|
+
// request. It has to come from the caller because #request is a private
|
|
11
|
+
// class field method -- this module can't call it directly.
|
|
12
|
+
|
|
13
|
+
// Keyed per SDK instance: a shared module-level variable would let one
|
|
14
|
+
// SDK instance's refresh starve a concurrent, unrelated SDK instance's own
|
|
15
|
+
// 401 recovery (a different user/session in the same Node process -- the
|
|
16
|
+
// API itself increasingly consumes this SDK server-side). Each instance
|
|
17
|
+
// still single-flights its own concurrent 401s against itself.
|
|
18
|
+
const inFlightRefreshBySdk = new WeakMap();
|
|
19
|
+
|
|
20
|
+
function isLoginRoute(endpoint) {
|
|
21
|
+
return typeof endpoint === 'string' && endpoint.startsWith('/login');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shouldAttemptRefresh(sdk, status, endpoint) {
|
|
25
|
+
return (
|
|
26
|
+
status === 401 &&
|
|
27
|
+
sdk?._autoRefresh === true &&
|
|
28
|
+
!isLoginRoute(endpoint) &&
|
|
29
|
+
typeof sdk?.login?.refresh === 'function'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function runRefresh(sdk) {
|
|
34
|
+
let inFlight = inFlightRefreshBySdk.get(sdk);
|
|
35
|
+
if (!inFlight) {
|
|
36
|
+
// Bearer clients (constructed with a raw token, no browser cookie jar)
|
|
37
|
+
// have to hand their raw refresh token back on every call -- the SDK
|
|
38
|
+
// never gets one implicitly the way a cookie client's browser does.
|
|
39
|
+
// sdk._refreshToken is set from login/refresh's own response below, or
|
|
40
|
+
// may be seeded via sdk.setRefreshToken() by a caller that obtained it
|
|
41
|
+
// out of band. Cookie clients simply have no stored value: `undefined`
|
|
42
|
+
// is fine, refresh() omits body.refreshToken and relies on the cookie.
|
|
43
|
+
inFlight = sdk.login
|
|
44
|
+
.refresh(sdk._refreshToken)
|
|
45
|
+
.then((result) => {
|
|
46
|
+
// Bearer clients get a new access token AND a new (rotated) refresh
|
|
47
|
+
// token back in the body; cookie clients rely on the Set-Cookie
|
|
48
|
+
// pair the refresh response already sent -- there is no token
|
|
49
|
+
// string to apply to the SDK instance for them.
|
|
50
|
+
if (result?.token) sdk.setToken(result.token);
|
|
51
|
+
if (result?.refreshToken) sdk._refreshToken = result.refreshToken;
|
|
52
|
+
return result;
|
|
53
|
+
})
|
|
54
|
+
.finally(() => {
|
|
55
|
+
inFlightRefreshBySdk.delete(sdk);
|
|
56
|
+
});
|
|
57
|
+
inFlightRefreshBySdk.set(sdk, inFlight);
|
|
58
|
+
}
|
|
59
|
+
return inFlight;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function notifyUnauthorized(sdk, err) {
|
|
63
|
+
if (typeof sdk?._onUnauthorized !== 'function') return;
|
|
64
|
+
try {
|
|
65
|
+
sdk._onUnauthorized(err);
|
|
66
|
+
} catch (callbackErr) {
|
|
67
|
+
console.error(
|
|
68
|
+
'refreshInterceptor :: onUnauthorized callback threw ::',
|
|
69
|
+
callbackErr,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {object} sdk
|
|
76
|
+
* @param {object} ctx
|
|
77
|
+
* @param {number} ctx.status
|
|
78
|
+
* @param {string} ctx.endpoint
|
|
79
|
+
* @param {() => Promise<any>} ctx.retry
|
|
80
|
+
* @param {Error} ctx.originalError -- rethrown whenever refresh isn't
|
|
81
|
+
* attempted, fails, or the retry itself still comes back 401 (never loop
|
|
82
|
+
* past one retry).
|
|
83
|
+
* @param {boolean} [ctx.alreadyRetried]
|
|
84
|
+
*/
|
|
85
|
+
export async function handleUnauthorized(
|
|
86
|
+
sdk,
|
|
87
|
+
{ status, endpoint, retry, originalError, alreadyRetried = false },
|
|
88
|
+
) {
|
|
89
|
+
if (alreadyRetried || !shouldAttemptRefresh(sdk, status, endpoint)) {
|
|
90
|
+
throw originalError;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await runRefresh(sdk);
|
|
95
|
+
} catch (refreshErr) {
|
|
96
|
+
notifyUnauthorized(sdk, originalError);
|
|
97
|
+
throw originalError;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return await retry();
|
|
102
|
+
} catch (retryErr) {
|
|
103
|
+
if (retryErr?.status === 401) notifyUnauthorized(sdk, retryErr);
|
|
104
|
+
throw retryErr;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.112-security.0",
|
|
4
4
|
"description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"*.js",
|
|
41
41
|
"services/**/*.js",
|
|
42
42
|
"transports/**/*.js",
|
|
43
|
+
"lib/**/*.js",
|
|
43
44
|
"types/**/*.d.ts",
|
|
44
45
|
"proto/**/*.proto",
|
|
45
46
|
"schemas/**/*.js",
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { internalRequest } from '../base.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Entitlements / license administration (W2, plan §2 frozen contract).
|
|
5
|
+
* Tenant-admin methods hit `/permissions/*` (admin:license:manage /
|
|
6
|
+
* admin:user:manage, real guard in the controller). Brand-owner methods
|
|
7
|
+
* hit `/permissions/brand/*` (requireBrandOwner + brandId ownership
|
|
8
|
+
* check, 2D). Neither is tenant-writable for seats/overrides/AI --
|
|
9
|
+
* those live under `/internal/authz/*` for account-builder/billing and
|
|
10
|
+
* are not exposed here.
|
|
11
|
+
*/
|
|
12
|
+
export class LicensesService {
|
|
13
|
+
constructor(sdk) {
|
|
14
|
+
this.sdk = sdk;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the tenant's license catalog: license types, seat usage, and the
|
|
19
|
+
* capability catalog.
|
|
20
|
+
* @returns {Promise<Object>} `{ mode, licenseTypes[], capabilities[] }`
|
|
21
|
+
* @example
|
|
22
|
+
* const { licenseTypes } = await sdk.licenses.getCatalog();
|
|
23
|
+
*/
|
|
24
|
+
async getCatalog() {
|
|
25
|
+
return internalRequest(this.sdk, '/permissions/licenses', 'GET');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* List users stuck in `needs_seat` (no free seat at assign time).
|
|
30
|
+
* @param {string} [licenseTypeCode] - Filter to one license type
|
|
31
|
+
* @returns {Promise<Object>} `{ results: [{ userId, licenseTypeCode, source, createdAt }] }`
|
|
32
|
+
* @example
|
|
33
|
+
* const { results } = await sdk.licenses.listNeedsSeat('license.support');
|
|
34
|
+
*/
|
|
35
|
+
async listNeedsSeat(licenseTypeCode) {
|
|
36
|
+
const params = licenseTypeCode ? { query: { licenseTypeCode } } : {};
|
|
37
|
+
return internalRequest(
|
|
38
|
+
this.sdk,
|
|
39
|
+
'/permissions/licenses/needs-seat',
|
|
40
|
+
'GET',
|
|
41
|
+
params,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get one user's licenses and resolved entitlements.
|
|
47
|
+
* @param {string} userId
|
|
48
|
+
* @returns {Promise<Object>} `{ licenses[], capabilities[], disabledCapabilities[], licenseGate }`
|
|
49
|
+
* @example
|
|
50
|
+
* await sdk.licenses.getUserLicenses('user-123');
|
|
51
|
+
*/
|
|
52
|
+
async getUserLicenses(userId) {
|
|
53
|
+
this.sdk.validateParams(
|
|
54
|
+
{ userId },
|
|
55
|
+
{ userId: { type: 'string', required: true } },
|
|
56
|
+
);
|
|
57
|
+
return internalRequest(
|
|
58
|
+
this.sdk,
|
|
59
|
+
`/permissions/users/${userId}/licenses`,
|
|
60
|
+
'GET',
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Assign a license directly to a user. 409s with
|
|
66
|
+
* `code:'seat_limit_reached'` at capacity or `code:'license_type_disabled'`
|
|
67
|
+
* for a disabled type.
|
|
68
|
+
* @param {string} userId
|
|
69
|
+
* @param {string} licenseTypeCode
|
|
70
|
+
* @returns {Promise<Object>} The assigned license row
|
|
71
|
+
* @example
|
|
72
|
+
* await sdk.licenses.assignUserLicense('user-123', 'license.core');
|
|
73
|
+
*/
|
|
74
|
+
async assignUserLicense(userId, licenseTypeCode) {
|
|
75
|
+
this.sdk.validateParams(
|
|
76
|
+
{ userId, licenseTypeCode },
|
|
77
|
+
{
|
|
78
|
+
userId: { type: 'string', required: true },
|
|
79
|
+
licenseTypeCode: { type: 'string', required: true },
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
return internalRequest(
|
|
83
|
+
this.sdk,
|
|
84
|
+
`/permissions/users/${userId}/licenses`,
|
|
85
|
+
'POST',
|
|
86
|
+
{ body: { licenseTypeCode } },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Remove a user's direct license grant (group-sourced grants are
|
|
92
|
+
* unaffected -- see `recomputeUserLicenses`).
|
|
93
|
+
* @param {string} userId
|
|
94
|
+
* @param {string} licenseTypeCode
|
|
95
|
+
* @returns {Promise<Object>}
|
|
96
|
+
* @example
|
|
97
|
+
* await sdk.licenses.removeUserLicense('user-123', 'license.core');
|
|
98
|
+
*/
|
|
99
|
+
async removeUserLicense(userId, licenseTypeCode) {
|
|
100
|
+
this.sdk.validateParams(
|
|
101
|
+
{ userId, licenseTypeCode },
|
|
102
|
+
{
|
|
103
|
+
userId: { type: 'string', required: true },
|
|
104
|
+
licenseTypeCode: { type: 'string', required: true },
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
return internalRequest(
|
|
108
|
+
this.sdk,
|
|
109
|
+
`/permissions/users/${userId}/licenses/${licenseTypeCode}`,
|
|
110
|
+
'DELETE',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a group's license types (group-sourced grants fan out to members).
|
|
116
|
+
* @param {string} groupId
|
|
117
|
+
* @returns {Promise<Object>} `{ licenseTypeCodes: [] }`
|
|
118
|
+
* @example
|
|
119
|
+
* await sdk.licenses.getGroupLicenses('group-123');
|
|
120
|
+
*/
|
|
121
|
+
async getGroupLicenses(groupId) {
|
|
122
|
+
this.sdk.validateParams(
|
|
123
|
+
{ groupId },
|
|
124
|
+
{ groupId: { type: 'string', required: true } },
|
|
125
|
+
);
|
|
126
|
+
return internalRequest(
|
|
127
|
+
this.sdk,
|
|
128
|
+
`/permissions/groups/${groupId}/licenses`,
|
|
129
|
+
'GET',
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Replace a group's license types. Triggers a `recomputeUserLicenses`
|
|
135
|
+
* fan-out for every member.
|
|
136
|
+
* @param {string} groupId
|
|
137
|
+
* @param {string[]} licenseTypeCodes
|
|
138
|
+
* @returns {Promise<Object>}
|
|
139
|
+
* @example
|
|
140
|
+
* await sdk.licenses.setGroupLicenses('group-123', ['license.core']);
|
|
141
|
+
*/
|
|
142
|
+
async setGroupLicenses(groupId, licenseTypeCodes) {
|
|
143
|
+
this.sdk.validateParams(
|
|
144
|
+
{ groupId, licenseTypeCodes },
|
|
145
|
+
{
|
|
146
|
+
groupId: { type: 'string', required: true },
|
|
147
|
+
licenseTypeCodes: { type: 'array', required: true },
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
return internalRequest(
|
|
151
|
+
this.sdk,
|
|
152
|
+
`/permissions/groups/${groupId}/licenses`,
|
|
153
|
+
'PUT',
|
|
154
|
+
{ body: { licenseTypeCodes } },
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Set a user's active/disabled status (W1-3). Disabling bumps the
|
|
160
|
+
* user's token_version and logs them out of every queue.
|
|
161
|
+
* @param {string} userId
|
|
162
|
+
* @param {'active'|'disabled'} status
|
|
163
|
+
* @returns {Promise<Object>}
|
|
164
|
+
* @example
|
|
165
|
+
* await sdk.licenses.setUserStatus('user-123', 'disabled');
|
|
166
|
+
*/
|
|
167
|
+
async setUserStatus(userId, status) {
|
|
168
|
+
this.sdk.validateParams(
|
|
169
|
+
{ userId, status },
|
|
170
|
+
{
|
|
171
|
+
userId: { type: 'string', required: true },
|
|
172
|
+
status: { type: 'string', required: true },
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
return internalRequest(
|
|
176
|
+
this.sdk,
|
|
177
|
+
`/permissions/users/${userId}/status`,
|
|
178
|
+
'PUT',
|
|
179
|
+
{ body: { status } },
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// -- Brand-owner methods (requireBrandOwner; 2D api routes) --------------
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Brand owner: list the tenant accounts under this brand, with their
|
|
187
|
+
* seats and overrides.
|
|
188
|
+
* @returns {Promise<Object>} `{ accounts: [{ id, name, code, aiEnabled, seats[], overrides[] }] }`
|
|
189
|
+
* @example
|
|
190
|
+
* const { accounts } = await sdk.licenses.listBrandAccounts();
|
|
191
|
+
*/
|
|
192
|
+
async listBrandAccounts() {
|
|
193
|
+
return internalRequest(this.sdk, '/permissions/brand/accounts', 'GET');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Brand owner: get one brand account's entitlements.
|
|
198
|
+
* @param {string} accountId
|
|
199
|
+
* @returns {Promise<Object>} `{ id, name, code, aiEnabled, seats[], overrides[] }`
|
|
200
|
+
* @example
|
|
201
|
+
* await sdk.licenses.getBrandAccountEntitlements('acct-123');
|
|
202
|
+
*/
|
|
203
|
+
async getBrandAccountEntitlements(accountId) {
|
|
204
|
+
this.sdk.validateParams(
|
|
205
|
+
{ accountId },
|
|
206
|
+
{ accountId: { type: 'string', required: true } },
|
|
207
|
+
);
|
|
208
|
+
return internalRequest(
|
|
209
|
+
this.sdk,
|
|
210
|
+
`/permissions/brand/accounts/${accountId}/entitlements`,
|
|
211
|
+
'GET',
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Brand owner: set a brand account's seat pool for one license type.
|
|
217
|
+
* @param {string} accountId
|
|
218
|
+
* @param {string} licenseTypeCode
|
|
219
|
+
* @param {Object} seats
|
|
220
|
+
* @param {number} seats.seatCount
|
|
221
|
+
* @param {boolean} [seats.enabled]
|
|
222
|
+
* @param {boolean} [seats.isDefaultForNewUsers]
|
|
223
|
+
* @returns {Promise<Object>}
|
|
224
|
+
* @example
|
|
225
|
+
* await sdk.licenses.setBrandAccountSeats('acct-123', 'license.core', { seatCount: 25 });
|
|
226
|
+
*/
|
|
227
|
+
async setBrandAccountSeats(
|
|
228
|
+
accountId,
|
|
229
|
+
licenseTypeCode,
|
|
230
|
+
{ seatCount, enabled, isDefaultForNewUsers } = {},
|
|
231
|
+
) {
|
|
232
|
+
this.sdk.validateParams(
|
|
233
|
+
{ accountId, licenseTypeCode, seatCount },
|
|
234
|
+
{
|
|
235
|
+
accountId: { type: 'string', required: true },
|
|
236
|
+
licenseTypeCode: { type: 'string', required: true },
|
|
237
|
+
seatCount: { type: 'number', required: true },
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
const body = { seatCount };
|
|
241
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
242
|
+
if (isDefaultForNewUsers !== undefined)
|
|
243
|
+
body.isDefaultForNewUsers = isDefaultForNewUsers;
|
|
244
|
+
return internalRequest(
|
|
245
|
+
this.sdk,
|
|
246
|
+
`/permissions/brand/accounts/${accountId}/license-seats/${licenseTypeCode}`,
|
|
247
|
+
'PUT',
|
|
248
|
+
{ body },
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Brand owner: enable or disable a capability override for a brand
|
|
254
|
+
* account.
|
|
255
|
+
* @param {string} accountId
|
|
256
|
+
* @param {string} capabilityCode
|
|
257
|
+
* @param {Object} override
|
|
258
|
+
* @param {'enable'|'disable'} override.mode
|
|
259
|
+
* @param {string} [override.reason]
|
|
260
|
+
* @returns {Promise<Object>}
|
|
261
|
+
* @example
|
|
262
|
+
* await sdk.licenses.setBrandAccountOverride('acct-123', 'capability.chat', { mode: 'disable', reason: 'trial ended' });
|
|
263
|
+
*/
|
|
264
|
+
async setBrandAccountOverride(accountId, capabilityCode, { mode, reason } = {}) {
|
|
265
|
+
this.sdk.validateParams(
|
|
266
|
+
{ accountId, capabilityCode, mode },
|
|
267
|
+
{
|
|
268
|
+
accountId: { type: 'string', required: true },
|
|
269
|
+
capabilityCode: { type: 'string', required: true },
|
|
270
|
+
mode: { type: 'string', required: true },
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
const body = { mode };
|
|
274
|
+
if (reason !== undefined) body.reason = reason;
|
|
275
|
+
return internalRequest(
|
|
276
|
+
this.sdk,
|
|
277
|
+
`/permissions/brand/accounts/${accountId}/capability-overrides/${capabilityCode}`,
|
|
278
|
+
'PUT',
|
|
279
|
+
{ body },
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Brand owner: remove a capability override, reverting to the license
|
|
285
|
+
* template default.
|
|
286
|
+
* @param {string} accountId
|
|
287
|
+
* @param {string} capabilityCode
|
|
288
|
+
* @returns {Promise<Object>}
|
|
289
|
+
* @example
|
|
290
|
+
* await sdk.licenses.deleteBrandAccountOverride('acct-123', 'capability.chat');
|
|
291
|
+
*/
|
|
292
|
+
async deleteBrandAccountOverride(accountId, capabilityCode) {
|
|
293
|
+
this.sdk.validateParams(
|
|
294
|
+
{ accountId, capabilityCode },
|
|
295
|
+
{
|
|
296
|
+
accountId: { type: 'string', required: true },
|
|
297
|
+
capabilityCode: { type: 'string', required: true },
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
return internalRequest(
|
|
301
|
+
this.sdk,
|
|
302
|
+
`/permissions/brand/accounts/${accountId}/capability-overrides/${capabilityCode}`,
|
|
303
|
+
'DELETE',
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Brand owner: turn the account's AI features on/off.
|
|
309
|
+
* @param {string} accountId
|
|
310
|
+
* @param {boolean} enabled
|
|
311
|
+
* @returns {Promise<Object>}
|
|
312
|
+
* @example
|
|
313
|
+
* await sdk.licenses.setBrandAccountAiEnabled('acct-123', true);
|
|
314
|
+
*/
|
|
315
|
+
async setBrandAccountAiEnabled(accountId, enabled) {
|
|
316
|
+
this.sdk.validateParams(
|
|
317
|
+
{ accountId, enabled },
|
|
318
|
+
{
|
|
319
|
+
accountId: { type: 'string', required: true },
|
|
320
|
+
enabled: { type: 'boolean', required: true },
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
return internalRequest(
|
|
324
|
+
this.sdk,
|
|
325
|
+
`/permissions/brand/accounts/${accountId}/ai-enabled`,
|
|
326
|
+
'PUT',
|
|
327
|
+
{ body: { enabled } },
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
package/services/login.js
CHANGED
|
@@ -29,14 +29,35 @@ export class LoginService {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// api#222: bearer clients get refreshToken back on /login too (same
|
|
33
|
+
// contract as /login/refresh) -- seed it onto the sdk instance so
|
|
34
|
+
// autoRefresh has something to send on the very first refresh, and
|
|
35
|
+
// also return it for a caller that manages the token itself.
|
|
36
|
+
if (login?.refreshToken) this.sdk.setRefreshToken(login.refreshToken);
|
|
37
|
+
|
|
32
38
|
return {
|
|
33
39
|
valid: true,
|
|
34
40
|
userId: login.userId,
|
|
35
41
|
namespace: login.namespace,
|
|
36
42
|
url: login.url,
|
|
43
|
+
refreshToken: login.refreshToken,
|
|
37
44
|
};
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
// api#222. Cookie clients: no args -- the refreshToken cookie is sent
|
|
48
|
+
// automatically and the new authToken/refreshToken cookies come back on
|
|
49
|
+
// the response, nothing to apply to the SDK instance. Bearer clients:
|
|
50
|
+
// pass the refresh token string; the response's `token` is returned so
|
|
51
|
+
// the caller (or lib/refreshInterceptor.js) can call sdk.setToken(token).
|
|
52
|
+
// Always forceFetch (true): refresh exists to recover a dead session, so
|
|
53
|
+
// it must never ride a socket transport that itself depends on that
|
|
54
|
+
// session still being alive.
|
|
55
|
+
async refresh(refreshToken) {
|
|
56
|
+
const options = {};
|
|
57
|
+
if (refreshToken) options.body = { refreshToken };
|
|
58
|
+
return internalRequest(this.sdk, '/login/refresh', 'POST', options, true);
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
async logout() {
|
|
41
62
|
const logout = await internalRequest(this.sdk, '/login', 'DELETE', {}, true);
|
|
42
63
|
|
package/services/objects.js
CHANGED
|
@@ -1416,44 +1416,4 @@ export class ObjectsService {
|
|
|
1416
1416
|
{ body: {} },
|
|
1417
1417
|
);
|
|
1418
1418
|
}
|
|
1419
|
-
|
|
1420
|
-
/**
|
|
1421
|
-
* P7 (workflows-v2-plan.md W10/W11/W23) -- runs an immediate full
|
|
1422
|
-
* enter+exit sweep for a journey program. Same diff-and-enqueue path a
|
|
1423
|
-
* scheduled tick runs, so this can never 429.
|
|
1424
|
-
* @param {string} programId
|
|
1425
|
-
* @returns {Promise<{queued: true, enterEnqueued: number, exitEnqueued: number}>}
|
|
1426
|
-
*/
|
|
1427
|
-
async runMarketingProgramNow(programId) {
|
|
1428
|
-
this.sdk.validateParams(
|
|
1429
|
-
{ programId },
|
|
1430
|
-
{ programId: { type: 'string', required: true } },
|
|
1431
|
-
);
|
|
1432
|
-
return internalRequest(
|
|
1433
|
-
this.sdk,
|
|
1434
|
-
`/object/marketing-programs/${programId}/run`,
|
|
1435
|
-
'POST',
|
|
1436
|
-
{ body: {} },
|
|
1437
|
-
);
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
/**
|
|
1441
|
-
* P7 -- paginated member list for a journey program (joined to people
|
|
1442
|
-
* for display name; never raw peopleId).
|
|
1443
|
-
* @param {string} programId
|
|
1444
|
-
* @param {{nextId?: string, limit?: number}} [opts]
|
|
1445
|
-
* @returns {Promise<{results: object[], pagination: object}>}
|
|
1446
|
-
*/
|
|
1447
|
-
async listMarketingProgramMembers(programId, opts = {}) {
|
|
1448
|
-
this.sdk.validateParams(
|
|
1449
|
-
{ programId },
|
|
1450
|
-
{ programId: { type: 'string', required: true } },
|
|
1451
|
-
);
|
|
1452
|
-
return internalRequest(
|
|
1453
|
-
this.sdk,
|
|
1454
|
-
`/object/marketing-programs/${programId}/members`,
|
|
1455
|
-
'GET',
|
|
1456
|
-
{ query: opts },
|
|
1457
|
-
);
|
|
1458
|
-
}
|
|
1459
1419
|
}
|
package/services/workflows.js
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { internalRequest } from '../base.js';
|
|
2
|
-
import { WorkflowToolsService, WorkflowMcpTokensService } from './workflowTools.js';
|
|
3
|
-
|
|
4
2
|
export class WorkflowsService {
|
|
5
3
|
constructor(sdk) {
|
|
6
4
|
this.sdk = sdk;
|
|
7
5
|
this.items = new WorkflowItemsService(sdk);
|
|
8
6
|
this.connections = new WorkflowConnectionsService(sdk);
|
|
9
7
|
this.sessions = new WorkflowSessionsService(sdk);
|
|
10
|
-
// P5: MCP tools/tokens surface (workflows-v2-plan.md "REST twin + SDK" row).
|
|
11
|
-
this.tools = new WorkflowToolsService(sdk);
|
|
12
|
-
this.mcpTokens = new WorkflowMcpTokensService(sdk);
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
async
|
|
10
|
+
async getSettings(type) {
|
|
11
|
+
this.sdk.validateParams(
|
|
12
|
+
{ type },
|
|
13
|
+
{
|
|
14
|
+
type: { type: 'string', required: true },
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
16
18
|
const params = {
|
|
17
|
-
query:
|
|
19
|
+
query: { type },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const result = await internalRequest(this.sdk, '/workflows/settings', 'GET', params);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async listModules() {
|
|
27
|
+
const params = {
|
|
28
|
+
query: {},
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
const result = await internalRequest(this.sdk, '/workflows/modules', 'GET', params);
|
|
@@ -96,48 +107,6 @@ export class WorkflowsService {
|
|
|
96
107
|
);
|
|
97
108
|
return result;
|
|
98
109
|
}
|
|
99
|
-
|
|
100
|
-
// Delete guard (soft-delete workflows / hard-delete draft versions):
|
|
101
|
-
// resolves everything a workflow (or a specific draft version) is
|
|
102
|
-
// referenced by, so the client can block the delete with a named list
|
|
103
|
-
// instead of a generic FK error. Exactly one of workflowId/
|
|
104
|
-
// workflowVersionId is expected.
|
|
105
|
-
async references({ workflowId, workflowVersionId } = {}) {
|
|
106
|
-
this.sdk.validateParams(
|
|
107
|
-
{ workflowId, workflowVersionId },
|
|
108
|
-
{
|
|
109
|
-
workflowId: { type: 'string', required: false },
|
|
110
|
-
workflowVersionId: { type: 'string', required: false },
|
|
111
|
-
},
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const query = {};
|
|
115
|
-
if (workflowId) query.workflowId = workflowId;
|
|
116
|
-
if (workflowVersionId) query.workflowVersionId = workflowVersionId;
|
|
117
|
-
|
|
118
|
-
const result = await internalRequest(this.sdk, '/workflows/references', 'GET', {
|
|
119
|
-
query,
|
|
120
|
-
});
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// P6 §6 — variable catalogue (inputs/system/context/module outputs) for
|
|
125
|
-
// the designer's `{{` autocomplete + variables panel.
|
|
126
|
-
async variables(versionId) {
|
|
127
|
-
this.sdk.validateParams(
|
|
128
|
-
{ versionId },
|
|
129
|
-
{
|
|
130
|
-
versionId: { type: 'string', required: true },
|
|
131
|
-
},
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const result = await internalRequest(
|
|
135
|
-
this.sdk,
|
|
136
|
-
`/workflows/${versionId}/variables`,
|
|
137
|
-
'GET',
|
|
138
|
-
);
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
110
|
}
|
|
142
111
|
|
|
143
112
|
export class WorkflowItemsService {
|
|
@@ -434,10 +403,11 @@ export class WorkflowSessionsService {
|
|
|
434
403
|
},
|
|
435
404
|
);
|
|
436
405
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
406
|
+
const result = await internalRequest(this.sdk,
|
|
407
|
+
`/workflows/sessions/${sessionId}`,
|
|
408
|
+
'GET',
|
|
409
|
+
);
|
|
410
|
+
return result;
|
|
441
411
|
}
|
|
442
412
|
|
|
443
413
|
async update(sessionId, updateData) {
|
|
@@ -461,25 +431,17 @@ export class WorkflowSessionsService {
|
|
|
461
431
|
return result;
|
|
462
432
|
}
|
|
463
433
|
|
|
464
|
-
|
|
465
|
-
* @param {string} sessionId
|
|
466
|
-
* @param {string} [reason] - passed through as body.reason -- the
|
|
467
|
-
* sessionComplete.js controller already accepts this (defaults to
|
|
468
|
-
* 'completed' server-side); P7's exitProgramMember.js passes
|
|
469
|
-
* 'programExit'. Backward compatible -- omitting it is unchanged.
|
|
470
|
-
*/
|
|
471
|
-
async complete(sessionId, reason) {
|
|
434
|
+
async complete(sessionId) {
|
|
472
435
|
this.sdk.validateParams(
|
|
473
|
-
{ sessionId
|
|
436
|
+
{ sessionId },
|
|
474
437
|
{
|
|
475
438
|
sessionId: { type: 'string', required: true },
|
|
476
|
-
reason: { type: 'string', required: false },
|
|
477
439
|
},
|
|
478
440
|
);
|
|
479
441
|
|
|
480
|
-
const params =
|
|
442
|
+
const params = {};
|
|
481
443
|
|
|
482
|
-
const result = await internalRequest(this.sdk,
|
|
444
|
+
const result = await internalRequest(this.sdk,
|
|
483
445
|
`/workflows/session/${sessionId}/complete`,
|
|
484
446
|
'PUT',
|
|
485
447
|
params,
|
|
@@ -487,6 +449,21 @@ export class WorkflowSessionsService {
|
|
|
487
449
|
return result;
|
|
488
450
|
}
|
|
489
451
|
|
|
452
|
+
async delete(sessionId) {
|
|
453
|
+
this.sdk.validateParams(
|
|
454
|
+
{ sessionId },
|
|
455
|
+
{
|
|
456
|
+
sessionId: { type: 'string', required: true },
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const result = await internalRequest(this.sdk,
|
|
461
|
+
`/workflows/sessions/${sessionId}`,
|
|
462
|
+
'DELETE',
|
|
463
|
+
);
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
|
|
490
467
|
async logTranscript(sessionId, transcriptData) {
|
|
491
468
|
this.sdk.validateParams(
|
|
492
469
|
{ sessionId },
|
|
@@ -536,41 +513,11 @@ export class WorkflowSessionsService {
|
|
|
536
513
|
query: { startDate, endDate },
|
|
537
514
|
};
|
|
538
515
|
|
|
539
|
-
const result = await internalRequest(this.sdk,
|
|
516
|
+
const result = await internalRequest(this.sdk,
|
|
540
517
|
`/workflows/${workflowVersionId}/analytics`,
|
|
541
518
|
'GET',
|
|
542
519
|
params,
|
|
543
520
|
);
|
|
544
521
|
return result;
|
|
545
522
|
}
|
|
546
|
-
|
|
547
|
-
// P10 click-to-filter: GET /workflows/:workflowVersionId/sessions ->
|
|
548
|
-
// {sessionIds}. filter is {workflowItemId?, fromItemId?, toItemId?,
|
|
549
|
-
// startDate?, endDate?} -- workflowItemId alone, or fromItemId+toItemId
|
|
550
|
-
// together, is required (validated server-side in sessionsByPath.js).
|
|
551
|
-
async sessionsByPath(workflowVersionId, filter = {}) {
|
|
552
|
-
this.sdk.validateParams(
|
|
553
|
-
{ workflowVersionId },
|
|
554
|
-
{
|
|
555
|
-
workflowVersionId: { type: 'string', required: true },
|
|
556
|
-
},
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
const { workflowItemId, fromItemId, toItemId, startDate, endDate } =
|
|
560
|
-
filter || {};
|
|
561
|
-
const query = {};
|
|
562
|
-
if (workflowItemId) query.workflowItemId = workflowItemId;
|
|
563
|
-
if (fromItemId) query.fromItemId = fromItemId;
|
|
564
|
-
if (toItemId) query.toItemId = toItemId;
|
|
565
|
-
if (startDate) query.startDate = startDate;
|
|
566
|
-
if (endDate) query.endDate = endDate;
|
|
567
|
-
|
|
568
|
-
const result = await internalRequest(
|
|
569
|
-
this.sdk,
|
|
570
|
-
`/workflows/${workflowVersionId}/sessions`,
|
|
571
|
-
'GET',
|
|
572
|
-
{ query },
|
|
573
|
-
);
|
|
574
|
-
return result;
|
|
575
|
-
}
|
|
576
523
|
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { internalRequest } from '../base.js';
|
|
2
|
-
|
|
3
|
-
// P5 "REST twin + SDK" / "Auth" rows: workflows.tools.{list,call} +
|
|
4
|
-
// workflows.mcpTokens.{create,list,revoke}. Kept in its own file (repo
|
|
5
|
-
// file-size convention) rather than growing workflows.js further.
|
|
6
|
-
|
|
7
|
-
export class WorkflowToolsService {
|
|
8
|
-
constructor(sdk) {
|
|
9
|
-
this.sdk = sdk;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// GET /workflows/tools -> [{name, description, inputSchema, outputSchema, annotations}]
|
|
13
|
-
async list() {
|
|
14
|
-
const result = await internalRequest(this.sdk, '/workflows/tools', 'GET');
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// POST /workflows/tools/:slug/call -> {completed, outputs, isError, message, sessionId}
|
|
19
|
-
async call(slug, input) {
|
|
20
|
-
this.sdk.validateParams(
|
|
21
|
-
{ slug },
|
|
22
|
-
{ slug: { type: 'string', required: true } },
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
const params = { body: { input: input || {} } };
|
|
26
|
-
const result = await internalRequest(
|
|
27
|
-
this.sdk,
|
|
28
|
-
`/workflows/tools/${slug}/call`,
|
|
29
|
-
'POST',
|
|
30
|
-
params,
|
|
31
|
-
);
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class WorkflowMcpTokensService {
|
|
37
|
-
constructor(sdk) {
|
|
38
|
-
this.sdk = sdk;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// POST /workflows/mcp/tokens -> {id, token, name} -- `token` (the raw
|
|
42
|
-
// JWT) is shown exactly once, never retrievable again.
|
|
43
|
-
async create({ name } = {}) {
|
|
44
|
-
const params = { body: name ? { name } : {} };
|
|
45
|
-
const result = await internalRequest(
|
|
46
|
-
this.sdk,
|
|
47
|
-
'/workflows/mcp/tokens',
|
|
48
|
-
'POST',
|
|
49
|
-
params,
|
|
50
|
-
);
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// GET /workflows/mcp/tokens -> {tokens: [{id, name, createdAt, lastUsedAt, revokedAt}]}
|
|
55
|
-
async list() {
|
|
56
|
-
const result = await internalRequest(this.sdk, '/workflows/mcp/tokens', 'GET');
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// DELETE /workflows/mcp/tokens/:id -> {id, revoked:true}
|
|
61
|
-
async revoke(id) {
|
|
62
|
-
this.sdk.validateParams(
|
|
63
|
-
{ id },
|
|
64
|
-
{ id: { type: 'string', required: true } },
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const result = await internalRequest(
|
|
68
|
-
this.sdk,
|
|
69
|
-
`/workflows/mcp/tokens/${id}`,
|
|
70
|
-
'DELETE',
|
|
71
|
-
);
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
}
|