@nurama/sdk 1.4.0 → 1.4.1
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/NuramaClient.d.ts +32 -0
- package/dist/NuramaClient.d.ts.map +1 -1
- package/dist/NuramaClient.js +38 -0
- package/dist/NuramaClient.js.map +1 -1
- package/dist/browser/nurama-bot-sdk.js +299 -28
- package/dist/browser/nurama-bot-sdk.min.js +1 -1
- package/dist/browser/nurama-sdk.js +299 -28
- package/dist/browser/nurama-sdk.min.js +1 -1
- package/dist/routes/bot.d.ts +60 -5
- package/dist/routes/bot.d.ts.map +1 -1
- package/dist/routes/bot.js +72 -0
- package/dist/routes/bot.js.map +1 -1
- package/dist/routes/joinLink.d.ts +51 -1
- package/dist/routes/joinLink.d.ts.map +1 -1
- package/dist/routes/joinLink.js +124 -0
- package/dist/routes/joinLink.js.map +1 -1
- package/dist/routes/oauthGrant.d.ts +45 -0
- package/dist/routes/oauthGrant.d.ts.map +1 -0
- package/dist/routes/oauthGrant.js +32 -0
- package/dist/routes/oauthGrant.js.map +1 -0
- package/dist/routes/socket.d.ts.map +1 -1
- package/dist/routes/socket.js +47 -29
- package/dist/routes/socket.js.map +1 -1
- package/dist/routes/token.d.ts +23 -1
- package/dist/routes/token.d.ts.map +1 -1
- package/dist/routes/token.js +22 -0
- package/dist/routes/token.js.map +1 -1
- package/dist/version.js +4 -4
- package/package.json +2 -2
- package/src/NuramaClient.ts +60 -0
- package/src/routes/bot.ts +104 -5
- package/src/routes/joinLink.ts +133 -0
- package/src/routes/oauthGrant.ts +64 -0
- package/src/routes/socket.ts +51 -33
- package/src/routes/token.ts +40 -1
- package/src/version.ts +4 -4
package/src/routes/joinLink.ts
CHANGED
|
@@ -5,6 +5,16 @@ import {
|
|
|
5
5
|
type CreateJoinLinkPayload,
|
|
6
6
|
type UpdateJoinLinkPayload,
|
|
7
7
|
type RedeemJoinLinkResult,
|
|
8
|
+
type ListJoinRequestsResponse,
|
|
9
|
+
type ListMyJoinRequestsResponse,
|
|
10
|
+
type ApproveJoinRequestResponse,
|
|
11
|
+
type RejectJoinRequestRequest,
|
|
12
|
+
type RejectJoinRequestResponse,
|
|
13
|
+
type UpdateJoinRequestCooldownRequest,
|
|
14
|
+
type ProjectJoinRequest,
|
|
15
|
+
type ListBansResponse,
|
|
16
|
+
type CreateResourceBanRequest,
|
|
17
|
+
type ResourceBan,
|
|
8
18
|
} from '@nurama/types';
|
|
9
19
|
|
|
10
20
|
// --- Query Parameter Interfaces ---
|
|
@@ -49,6 +59,129 @@ export default function createJoinLinkMethods(client: NuramaClient) {
|
|
|
49
59
|
});
|
|
50
60
|
},
|
|
51
61
|
|
|
62
|
+
/** The queue of people waiting on a decision for this project. */
|
|
63
|
+
async getJoinRequests(params: { projectId: string }): Promise<ListJoinRequestsResponse> {
|
|
64
|
+
if (!params?.projectId) throw new Error('projectId is required.');
|
|
65
|
+
return client._request<ListJoinRequestsResponse>({
|
|
66
|
+
method: 'GET',
|
|
67
|
+
endpoint: '/v1/join-links/requests',
|
|
68
|
+
params,
|
|
69
|
+
sendJWT: true,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The caller's own requests still waiting on a decision.
|
|
75
|
+
*
|
|
76
|
+
* Needs no `projectId`: it is scoped server-side to the caller, who is not
|
|
77
|
+
* a member of the projects they are waiting on and so cannot read those
|
|
78
|
+
* projects' queues.
|
|
79
|
+
*/
|
|
80
|
+
async getMyJoinRequests(): Promise<ListMyJoinRequestsResponse> {
|
|
81
|
+
return client._request<ListMyJoinRequestsResponse>({
|
|
82
|
+
method: 'GET',
|
|
83
|
+
endpoint: '/v1/join-links/requests/mine',
|
|
84
|
+
sendJWT: true,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Approve a request and admit the person.
|
|
90
|
+
*
|
|
91
|
+
* Seats and the link's remaining uses are re-checked here, not when the
|
|
92
|
+
* request was made — a queue outlives the capacity that existed when it was
|
|
93
|
+
* filled, so approval can legitimately fail on a seat limit.
|
|
94
|
+
*/
|
|
95
|
+
async approveJoinRequest(requestId: string, projectId: string): Promise<ApproveJoinRequestResponse> {
|
|
96
|
+
if (!requestId) throw new Error('requestId is required.');
|
|
97
|
+
if (!projectId) throw new Error('projectId is required.');
|
|
98
|
+
return client._request<ApproveJoinRequestResponse>({
|
|
99
|
+
method: 'POST',
|
|
100
|
+
endpoint: `/v1/join-links/requests/${requestId}/approve`,
|
|
101
|
+
body: { projectId },
|
|
102
|
+
sendJWT: true,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reject a request, optionally barring the person.
|
|
108
|
+
*
|
|
109
|
+
* Passing `ban` is destructive beyond the refusal: a ban REMOVES an existing
|
|
110
|
+
* membership, so it can take away access the person already had in scope.
|
|
111
|
+
* It is a separate field for that reason, never implied by the rejection.
|
|
112
|
+
*/
|
|
113
|
+
async rejectJoinRequest(requestId: string, data: RejectJoinRequestRequest): Promise<RejectJoinRequestResponse> {
|
|
114
|
+
if (!requestId) throw new Error('requestId is required.');
|
|
115
|
+
if (!data?.projectId) throw new Error('projectId is required.');
|
|
116
|
+
return client._request<RejectJoinRequestResponse>({
|
|
117
|
+
method: 'POST',
|
|
118
|
+
endpoint: `/v1/join-links/requests/${requestId}/reject`,
|
|
119
|
+
body: data,
|
|
120
|
+
sendJWT: true,
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Change or lift the cooldown on a rejected request.
|
|
126
|
+
*
|
|
127
|
+
* Only a rejected request has one; `reapplyAfter: null` lifts it, letting
|
|
128
|
+
* the person ask again straight away. A barred person is refused — the ban
|
|
129
|
+
* already blocks every path, so lifting that is the action that matters.
|
|
130
|
+
*/
|
|
131
|
+
async updateJoinRequestCooldown(
|
|
132
|
+
requestId: string,
|
|
133
|
+
data: UpdateJoinRequestCooldownRequest,
|
|
134
|
+
): Promise<ProjectJoinRequest> {
|
|
135
|
+
if (!requestId) throw new Error('requestId is required.');
|
|
136
|
+
if (!data?.projectId) throw new Error('projectId is required.');
|
|
137
|
+
return client._request<ProjectJoinRequest>({
|
|
138
|
+
method: 'PATCH',
|
|
139
|
+
endpoint: `/v1/join-links/requests/${requestId}/cooldown`,
|
|
140
|
+
body: { projectId: data.projectId, reapplyAfter: data.reapplyAfter ?? null },
|
|
141
|
+
sendJWT: true,
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Bar someone, without a request to refuse.
|
|
147
|
+
*
|
|
148
|
+
* Destructive: the ban REMOVES every membership the person holds in its
|
|
149
|
+
* scope, so a `workspace` ban clears them out of every project in it. An
|
|
150
|
+
* owner cannot be barred, and neither can you bar yourself.
|
|
151
|
+
*/
|
|
152
|
+
async createResourceBan(data: CreateResourceBanRequest): Promise<ResourceBan> {
|
|
153
|
+
if (!data?.projectId) throw new Error('projectId is required.');
|
|
154
|
+
if (!data?.userId) throw new Error('userId is required.');
|
|
155
|
+
if (!data?.scope) throw new Error('scope is required.');
|
|
156
|
+
return client._request<ResourceBan>({
|
|
157
|
+
method: 'POST',
|
|
158
|
+
endpoint: '/v1/join-links/bans',
|
|
159
|
+
body: data,
|
|
160
|
+
sendJWT: true,
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/** People barred from this project, newest first. */
|
|
165
|
+
async getResourceBans(params: { projectId: string }): Promise<ListBansResponse> {
|
|
166
|
+
if (!params?.projectId) throw new Error('projectId is required.');
|
|
167
|
+
return client._request<ListBansResponse>({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
endpoint: '/v1/join-links/bans',
|
|
170
|
+
params,
|
|
171
|
+
sendJWT: true,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/** Lift a ban. It does not restore any membership the ban removed. */
|
|
176
|
+
async liftResourceBan(banId: string): Promise<ResourceBan> {
|
|
177
|
+
if (!banId) throw new Error('banId is required.');
|
|
178
|
+
return client._request<ResourceBan>({
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
endpoint: `/v1/join-links/bans/${banId}`,
|
|
181
|
+
sendJWT: true,
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
|
|
52
185
|
/** Update a join link's settings (role, mode, domains, expiry, enable/disable). */
|
|
53
186
|
async updateJoinLink(linkId: string, data: UpdateJoinLinkPayload): Promise<JoinLinkResponse> {
|
|
54
187
|
if (!linkId) throw new Error('linkId is required.');
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import NuramaClient from '../NuramaClient.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One application a user has authorised over OAuth.
|
|
5
|
+
*
|
|
6
|
+
* A grant is the record of consent: the scopes approved, and the tie binding
|
|
7
|
+
* every token issued under it. Revoking one withdraws the application's
|
|
8
|
+
* access.
|
|
9
|
+
*/
|
|
10
|
+
export interface OAuthGrantSummary {
|
|
11
|
+
/** Opaque identifier; pass to `revokeGrant`. */
|
|
12
|
+
grantId: string;
|
|
13
|
+
clientId: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* The application's name.
|
|
16
|
+
*
|
|
17
|
+
* Chosen by the application itself when `verified` is false — treat it as
|
|
18
|
+
* display text from an untrusted source, never as identification.
|
|
19
|
+
*/
|
|
20
|
+
clientName: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Whether Nurama configured this application.
|
|
23
|
+
*
|
|
24
|
+
* False for one that registered itself through Dynamic Client Registration:
|
|
25
|
+
* it picked its own name and nobody reviewed it.
|
|
26
|
+
*/
|
|
27
|
+
verified: boolean;
|
|
28
|
+
/** Scopes the user approved, from the backend's token-scope registry. */
|
|
29
|
+
scopes: string[];
|
|
30
|
+
grantedAt: string;
|
|
31
|
+
expiresAt: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function createOAuthGrantMethods(client: NuramaClient) {
|
|
35
|
+
return {
|
|
36
|
+
/**
|
|
37
|
+
* List the applications the caller has authorised, newest first.
|
|
38
|
+
* Expired grants are omitted by the server.
|
|
39
|
+
*/
|
|
40
|
+
async listGrants(): Promise<OAuthGrantSummary[]> {
|
|
41
|
+
const res = await client._request<{ results: OAuthGrantSummary[] }>({
|
|
42
|
+
method: 'GET',
|
|
43
|
+
endpoint: '/v1/oauth-grants',
|
|
44
|
+
sendJWT: true,
|
|
45
|
+
bypassCache: true,
|
|
46
|
+
});
|
|
47
|
+
return res.results;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Withdraw an application's access: the grant and every token issued
|
|
52
|
+
* under it. An access token already in flight stays valid until it
|
|
53
|
+
* expires, so revocation is not instantaneous for a request under way.
|
|
54
|
+
*/
|
|
55
|
+
async revokeGrant(grantId: string): Promise<void> {
|
|
56
|
+
if (!grantId) throw new Error('grantId is required.');
|
|
57
|
+
return client._request<void>({
|
|
58
|
+
method: 'DELETE',
|
|
59
|
+
endpoint: `/v1/oauth-grants/${grantId}`,
|
|
60
|
+
sendJWT: true,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/routes/socket.ts
CHANGED
|
@@ -110,6 +110,33 @@ const LIFECYCLE_LISTENER_EVENTS = new Set([
|
|
|
110
110
|
'reconnect',
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
|
+
/** First reconnect delay; socket.io doubles it per failed attempt, up to RECONNECTION_DELAY_MAX, ±50% jitter. */
|
|
114
|
+
const RECONNECTION_DELAY = 1000;
|
|
115
|
+
const RECONNECTION_DELAY_MAX = 30000;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Extra first-retry delay, up to this, after the SERVER closed the connection ("transport close"): a ws deploy
|
|
119
|
+
* or restart drops every client of that server at once, and without it they would all reconnect within the
|
|
120
|
+
* same second. Network drops ("ping timeout", "transport error") keep the normal 1s retry.
|
|
121
|
+
*/
|
|
122
|
+
const SERVER_CLOSE_RECONNECT_SPREAD = 10000;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Spread reconnects after a server-side close (see SERVER_CLOSE_RECONNECT_SPREAD). Sets the manager's next
|
|
126
|
+
* delay from the socket's 'disconnect', which socket.io emits before it schedules the retry, and restores the
|
|
127
|
+
* normal delay on 'connect'. Channels share one manager, so the last channel's draw wins, which is fine.
|
|
128
|
+
*/
|
|
129
|
+
const spreadReconnectAfterServerClose = (socket: any): void => {
|
|
130
|
+
const manager = socket.io;
|
|
131
|
+
if (typeof manager?.reconnectionDelay !== 'function') return;
|
|
132
|
+
socket.on('disconnect', (reason: string) => {
|
|
133
|
+
if (reason === 'transport close') {
|
|
134
|
+
manager.reconnectionDelay(RECONNECTION_DELAY + Math.random() * SERVER_CLOSE_RECONNECT_SPREAD);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
socket.on('connect', () => manager.reconnectionDelay(RECONNECTION_DELAY));
|
|
138
|
+
};
|
|
139
|
+
|
|
113
140
|
export default function createSocketMethods(client: NuramaClient) {
|
|
114
141
|
// Track active socket connections
|
|
115
142
|
const activeConnections: Record<string, SocketChannel> = {};
|
|
@@ -215,9 +242,12 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
215
242
|
|
|
216
243
|
// Reconnection configuration for resilience
|
|
217
244
|
reconnection: socketOptions.autoReconnect,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
245
|
+
// Never give up: the server may refuse connections for a while (busy, or a deploy draining), and after a
|
|
246
|
+
// final failure socket.io stops for good without telling the app (the manager's 'reconnect_failed' never
|
|
247
|
+
// reaches socket listeners). Retries settle at one every ~30s.
|
|
248
|
+
reconnectionAttempts: Infinity,
|
|
249
|
+
reconnectionDelay: RECONNECTION_DELAY,
|
|
250
|
+
reconnectionDelayMax: RECONNECTION_DELAY_MAX,
|
|
221
251
|
randomizationFactor: 0.5, // Add jitter to avoid thundering herd problem
|
|
222
252
|
|
|
223
253
|
// Connection timeout
|
|
@@ -244,14 +274,27 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
244
274
|
// handlers across reconnections, so they will fire once connected.
|
|
245
275
|
activeConnections[channel] = socketChannel;
|
|
246
276
|
|
|
277
|
+
spreadReconnectAfterServerClose(socket);
|
|
278
|
+
|
|
247
279
|
// Setup event listeners
|
|
248
280
|
return new Promise((resolve, reject) => {
|
|
249
281
|
let settled = false;
|
|
282
|
+
let hasConnected = false;
|
|
250
283
|
|
|
251
284
|
// Handle connection (initial or after reconnection)
|
|
252
285
|
socket.on('connect', () => {
|
|
253
286
|
_log(`Connected to channel: ${channel}`);
|
|
254
287
|
activeConnections[channel] = socketChannel;
|
|
288
|
+
if (hasConnected) {
|
|
289
|
+
// A second 'connect' on the same socket is socket.io reconnecting it (the manager emits 'reconnect',
|
|
290
|
+
// but socket listeners never see that). Notify app-level onReconnect listeners so they can
|
|
291
|
+
// REST-refetch any gap of server->client messages missed while the transport was down (socket.io has
|
|
292
|
+
// no replay across a reconnect).
|
|
293
|
+
_log(`Reconnected to channel ${channel}`);
|
|
294
|
+
const reconnectListeners = socketChannel.listeners['reconnect'] || new Set();
|
|
295
|
+
reconnectListeners.forEach(listener => listener({ type: 'reconnect' }));
|
|
296
|
+
}
|
|
297
|
+
hasConnected = true;
|
|
255
298
|
if (!settled) {
|
|
256
299
|
settled = true;
|
|
257
300
|
resolve(socketChannel);
|
|
@@ -270,23 +313,6 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
270
313
|
}
|
|
271
314
|
});
|
|
272
315
|
|
|
273
|
-
// Handle reconnection attempts
|
|
274
|
-
socket.on('reconnect_attempt', (attempt: number) => {
|
|
275
|
-
_log(`Reconnection attempt ${attempt} for channel: ${channel}`);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Handle successful reconnection
|
|
279
|
-
socket.on('reconnect', (attempt: number) => {
|
|
280
|
-
_log(`Reconnected to channel ${channel} after ${attempt} attempts`);
|
|
281
|
-
// Ensure activeConnections is up to date after reconnect
|
|
282
|
-
activeConnections[channel] = socketChannel;
|
|
283
|
-
// Notify app-level onReconnect listeners so they can REST-refetch any
|
|
284
|
-
// gap of server->client messages missed while the transport was down
|
|
285
|
-
// (socket.io has no replay across a reconnect).
|
|
286
|
-
const reconnectListeners = socketChannel.listeners['reconnect'] || new Set();
|
|
287
|
-
reconnectListeners.forEach(listener => listener({ type: 'reconnect', attempt }));
|
|
288
|
-
});
|
|
289
|
-
|
|
290
316
|
// Handle reconnection failure — only reject when socket.io gives up entirely
|
|
291
317
|
socket.on('reconnect_failed', () => {
|
|
292
318
|
_log(`Reconnection failed for channel ${channel} after maximum attempts`);
|
|
@@ -423,9 +449,9 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
423
449
|
|
|
424
450
|
// Reconnection configuration for resilience
|
|
425
451
|
reconnection: socketOptions.autoReconnect,
|
|
426
|
-
reconnectionAttempts:
|
|
427
|
-
reconnectionDelay:
|
|
428
|
-
reconnectionDelayMax:
|
|
452
|
+
reconnectionAttempts: Infinity, // as connect(): never give up silently
|
|
453
|
+
reconnectionDelay: RECONNECTION_DELAY,
|
|
454
|
+
reconnectionDelayMax: RECONNECTION_DELAY_MAX,
|
|
429
455
|
randomizationFactor: 0.5,
|
|
430
456
|
|
|
431
457
|
// Connection timeout
|
|
@@ -446,6 +472,8 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
446
472
|
listeners: {},
|
|
447
473
|
};
|
|
448
474
|
|
|
475
|
+
spreadReconnectAfterServerClose(socket);
|
|
476
|
+
|
|
449
477
|
// Setup event listeners
|
|
450
478
|
return new Promise((resolve, reject) => {
|
|
451
479
|
// Handle connection
|
|
@@ -461,16 +489,6 @@ export default function createSocketMethods(client: NuramaClient) {
|
|
|
461
489
|
reject(error);
|
|
462
490
|
});
|
|
463
491
|
|
|
464
|
-
// Handle reconnection attempts
|
|
465
|
-
socket.on('reconnect_attempt', (attempt: number) => {
|
|
466
|
-
_log(`Reconnection attempt ${attempt} for public channel: ${channel}`);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Handle successful reconnection
|
|
470
|
-
socket.on('reconnect', (attempt: number) => {
|
|
471
|
-
_log(`Reconnected to public channel ${channel} after ${attempt} attempts`);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
492
|
// Handle reconnection failure
|
|
475
493
|
socket.on('reconnect_failed', () => {
|
|
476
494
|
_log(`Reconnection failed for public channel ${channel} after maximum attempts`);
|
package/src/routes/token.ts
CHANGED
|
@@ -22,7 +22,14 @@ export type TokenScope =
|
|
|
22
22
|
| 'assets:read'
|
|
23
23
|
| 'assets:write'
|
|
24
24
|
| 'projects:read'
|
|
25
|
-
| '
|
|
25
|
+
| 'projects:write'
|
|
26
|
+
| 'workspaces:read'
|
|
27
|
+
| 'workspaces:write'
|
|
28
|
+
| 'convos:read'
|
|
29
|
+
| 'convos:write'
|
|
30
|
+
| 'notifications:read'
|
|
31
|
+
| 'notifications:write'
|
|
32
|
+
| 'ai:use';
|
|
26
33
|
|
|
27
34
|
/**
|
|
28
35
|
* The shape returned by `listTokens` and (without the `token` field)
|
|
@@ -50,6 +57,16 @@ export interface CreateTokenData {
|
|
|
50
57
|
expiresAt?: string | null;
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
export interface UpdateTokenScopesData {
|
|
61
|
+
/**
|
|
62
|
+
* The COMPLETE new scope set, not a delta — the same shape
|
|
63
|
+
* `createToken` takes, so what a token may do is always stated in
|
|
64
|
+
* full rather than accumulated through edits nobody can review.
|
|
65
|
+
* At least one scope.
|
|
66
|
+
*/
|
|
67
|
+
scopes: TokenScope[];
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
export interface CreateTokenResponse extends TokenSummary {
|
|
54
71
|
/**
|
|
55
72
|
* The raw secret. Returned ONLY in this response. The server keeps a
|
|
@@ -97,6 +114,28 @@ export default function createTokenMethods(client: NuramaClient) {
|
|
|
97
114
|
});
|
|
98
115
|
},
|
|
99
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Replace the scopes on one of the caller's Personal Access Tokens.
|
|
119
|
+
*
|
|
120
|
+
* `scopes` is the complete new set, not a delta. This exists because
|
|
121
|
+
* the scope registry grows over time: a token minted before a scope
|
|
122
|
+
* existed cannot hold it, and without this the only remedy is minting
|
|
123
|
+
* a replacement and reconfiguring everywhere the old one is used.
|
|
124
|
+
*
|
|
125
|
+
* The change takes effect on the token's very next request. Scopes
|
|
126
|
+
* remain a ceiling, never a floor — widening a token never lets it do
|
|
127
|
+
* anything its owner cannot already do.
|
|
128
|
+
*/
|
|
129
|
+
async updateTokenScopes(tokenId: string, data: UpdateTokenScopesData): Promise<TokenSummary> {
|
|
130
|
+
if (!tokenId) throw new Error('tokenId is required.');
|
|
131
|
+
return client._request<TokenSummary>({
|
|
132
|
+
method: 'PATCH',
|
|
133
|
+
endpoint: `/v1/tokens/${tokenId}`,
|
|
134
|
+
body: data,
|
|
135
|
+
sendJWT: true,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
|
|
100
139
|
/**
|
|
101
140
|
* Revoke one of the caller's Personal Access Tokens. The revocation
|
|
102
141
|
* is immediate — the token will return 401 on the very next request.
|
package/src/version.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface SDKVersionInfo {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const SDK_VERSION: SDKVersionInfo = {
|
|
17
|
-
version: '1.4.
|
|
18
|
-
buildTimestamp: '2026-10-
|
|
19
|
-
buildHash: '
|
|
20
|
-
gitCommit: '
|
|
17
|
+
version: '1.4.1',
|
|
18
|
+
buildTimestamp: '2026-10-10T06:58:40.258Z',
|
|
19
|
+
buildHash: 'f6505e2eb9725dc8d239bb401ced8f88',
|
|
20
|
+
gitCommit: '1c00e18',
|
|
21
21
|
dirty: false,
|
|
22
22
|
};
|