@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.
@@ -7880,6 +7880,118 @@
7880
7880
  sendJWT: true
7881
7881
  });
7882
7882
  },
7883
+ /** The queue of people waiting on a decision for this project. */
7884
+ async getJoinRequests(params) {
7885
+ if (!params?.projectId) throw new Error("projectId is required.");
7886
+ return client._request({
7887
+ method: "GET",
7888
+ endpoint: "/v1/join-links/requests",
7889
+ params,
7890
+ sendJWT: true
7891
+ });
7892
+ },
7893
+ /**
7894
+ * The caller's own requests still waiting on a decision.
7895
+ *
7896
+ * Needs no `projectId`: it is scoped server-side to the caller, who is not
7897
+ * a member of the projects they are waiting on and so cannot read those
7898
+ * projects' queues.
7899
+ */
7900
+ async getMyJoinRequests() {
7901
+ return client._request({
7902
+ method: "GET",
7903
+ endpoint: "/v1/join-links/requests/mine",
7904
+ sendJWT: true
7905
+ });
7906
+ },
7907
+ /**
7908
+ * Approve a request and admit the person.
7909
+ *
7910
+ * Seats and the link's remaining uses are re-checked here, not when the
7911
+ * request was made — a queue outlives the capacity that existed when it was
7912
+ * filled, so approval can legitimately fail on a seat limit.
7913
+ */
7914
+ async approveJoinRequest(requestId, projectId) {
7915
+ if (!requestId) throw new Error("requestId is required.");
7916
+ if (!projectId) throw new Error("projectId is required.");
7917
+ return client._request({
7918
+ method: "POST",
7919
+ endpoint: `/v1/join-links/requests/${requestId}/approve`,
7920
+ body: { projectId },
7921
+ sendJWT: true
7922
+ });
7923
+ },
7924
+ /**
7925
+ * Reject a request, optionally barring the person.
7926
+ *
7927
+ * Passing `ban` is destructive beyond the refusal: a ban REMOVES an existing
7928
+ * membership, so it can take away access the person already had in scope.
7929
+ * It is a separate field for that reason, never implied by the rejection.
7930
+ */
7931
+ async rejectJoinRequest(requestId, data) {
7932
+ if (!requestId) throw new Error("requestId is required.");
7933
+ if (!data?.projectId) throw new Error("projectId is required.");
7934
+ return client._request({
7935
+ method: "POST",
7936
+ endpoint: `/v1/join-links/requests/${requestId}/reject`,
7937
+ body: data,
7938
+ sendJWT: true
7939
+ });
7940
+ },
7941
+ /**
7942
+ * Change or lift the cooldown on a rejected request.
7943
+ *
7944
+ * Only a rejected request has one; `reapplyAfter: null` lifts it, letting
7945
+ * the person ask again straight away. A barred person is refused — the ban
7946
+ * already blocks every path, so lifting that is the action that matters.
7947
+ */
7948
+ async updateJoinRequestCooldown(requestId, data) {
7949
+ if (!requestId) throw new Error("requestId is required.");
7950
+ if (!data?.projectId) throw new Error("projectId is required.");
7951
+ return client._request({
7952
+ method: "PATCH",
7953
+ endpoint: `/v1/join-links/requests/${requestId}/cooldown`,
7954
+ body: { projectId: data.projectId, reapplyAfter: data.reapplyAfter ?? null },
7955
+ sendJWT: true
7956
+ });
7957
+ },
7958
+ /**
7959
+ * Bar someone, without a request to refuse.
7960
+ *
7961
+ * Destructive: the ban REMOVES every membership the person holds in its
7962
+ * scope, so a `workspace` ban clears them out of every project in it. An
7963
+ * owner cannot be barred, and neither can you bar yourself.
7964
+ */
7965
+ async createResourceBan(data) {
7966
+ if (!data?.projectId) throw new Error("projectId is required.");
7967
+ if (!data?.userId) throw new Error("userId is required.");
7968
+ if (!data?.scope) throw new Error("scope is required.");
7969
+ return client._request({
7970
+ method: "POST",
7971
+ endpoint: "/v1/join-links/bans",
7972
+ body: data,
7973
+ sendJWT: true
7974
+ });
7975
+ },
7976
+ /** People barred from this project, newest first. */
7977
+ async getResourceBans(params) {
7978
+ if (!params?.projectId) throw new Error("projectId is required.");
7979
+ return client._request({
7980
+ method: "GET",
7981
+ endpoint: "/v1/join-links/bans",
7982
+ params,
7983
+ sendJWT: true
7984
+ });
7985
+ },
7986
+ /** Lift a ban. It does not restore any membership the ban removed. */
7987
+ async liftResourceBan(banId) {
7988
+ if (!banId) throw new Error("banId is required.");
7989
+ return client._request({
7990
+ method: "DELETE",
7991
+ endpoint: `/v1/join-links/bans/${banId}`,
7992
+ sendJWT: true
7993
+ });
7994
+ },
7883
7995
  /** Update a join link's settings (role, mode, domains, expiry, enable/disable). */
7884
7996
  async updateJoinLink(linkId, data) {
7885
7997
  if (!linkId) throw new Error("linkId is required.");
@@ -8306,6 +8418,19 @@
8306
8418
  "reconnect_failed",
8307
8419
  "reconnect"
8308
8420
  ]);
8421
+ var RECONNECTION_DELAY = 1e3;
8422
+ var RECONNECTION_DELAY_MAX = 3e4;
8423
+ var SERVER_CLOSE_RECONNECT_SPREAD = 1e4;
8424
+ var spreadReconnectAfterServerClose = (socket) => {
8425
+ const manager = socket.io;
8426
+ if (typeof manager?.reconnectionDelay !== "function") return;
8427
+ socket.on("disconnect", (reason) => {
8428
+ if (reason === "transport close") {
8429
+ manager.reconnectionDelay(RECONNECTION_DELAY + Math.random() * SERVER_CLOSE_RECONNECT_SPREAD);
8430
+ }
8431
+ });
8432
+ socket.on("connect", () => manager.reconnectionDelay(RECONNECTION_DELAY));
8433
+ };
8309
8434
  function createSocketMethods(client) {
8310
8435
  const activeConnections = {};
8311
8436
  const debug = client.debug;
@@ -8359,12 +8484,12 @@
8359
8484
  query: { token },
8360
8485
  // Reconnection configuration for resilience
8361
8486
  reconnection: socketOptions.autoReconnect,
8362
- reconnectionAttempts: 10,
8363
- // Maximum reconnection attempts before giving up
8364
- reconnectionDelay: 1e3,
8365
- // Start with 1 second delay
8366
- reconnectionDelayMax: 3e4,
8367
- // Max delay of 30 seconds between attempts
8487
+ // Never give up: the server may refuse connections for a while (busy, or a deploy draining), and after a
8488
+ // final failure socket.io stops for good without telling the app (the manager's 'reconnect_failed' never
8489
+ // reaches socket listeners). Retries settle at one every ~30s.
8490
+ reconnectionAttempts: Infinity,
8491
+ reconnectionDelay: RECONNECTION_DELAY,
8492
+ reconnectionDelayMax: RECONNECTION_DELAY_MAX,
8368
8493
  randomizationFactor: 0.5,
8369
8494
  // Add jitter to avoid thundering herd problem
8370
8495
  // Connection timeout
@@ -8387,11 +8512,19 @@
8387
8512
  listeners: {}
8388
8513
  };
8389
8514
  activeConnections[channel] = socketChannel;
8515
+ spreadReconnectAfterServerClose(socket);
8390
8516
  return new Promise((resolve, reject) => {
8391
8517
  let settled = false;
8518
+ let hasConnected = false;
8392
8519
  socket.on("connect", () => {
8393
8520
  _log(`Connected to channel: ${channel}`);
8394
8521
  activeConnections[channel] = socketChannel;
8522
+ if (hasConnected) {
8523
+ _log(`Reconnected to channel ${channel}`);
8524
+ const reconnectListeners = socketChannel.listeners["reconnect"] || /* @__PURE__ */ new Set();
8525
+ reconnectListeners.forEach((listener) => listener({ type: "reconnect" }));
8526
+ }
8527
+ hasConnected = true;
8395
8528
  if (!settled) {
8396
8529
  settled = true;
8397
8530
  resolve(socketChannel);
@@ -8404,15 +8537,6 @@
8404
8537
  resolve(socketChannel);
8405
8538
  }
8406
8539
  });
8407
- socket.on("reconnect_attempt", (attempt) => {
8408
- _log(`Reconnection attempt ${attempt} for channel: ${channel}`);
8409
- });
8410
- socket.on("reconnect", (attempt) => {
8411
- _log(`Reconnected to channel ${channel} after ${attempt} attempts`);
8412
- activeConnections[channel] = socketChannel;
8413
- const reconnectListeners = socketChannel.listeners["reconnect"] || /* @__PURE__ */ new Set();
8414
- reconnectListeners.forEach((listener) => listener({ type: "reconnect", attempt }));
8415
- });
8416
8540
  socket.on("reconnect_failed", () => {
8417
8541
  _log(`Reconnection failed for channel ${channel} after maximum attempts`);
8418
8542
  const eventListeners = socketChannel.listeners["reconnect_failed"] || /* @__PURE__ */ new Set();
@@ -8492,9 +8616,10 @@
8492
8616
  query: { publicToken },
8493
8617
  // Reconnection configuration for resilience
8494
8618
  reconnection: socketOptions.autoReconnect,
8495
- reconnectionAttempts: 10,
8496
- reconnectionDelay: 1e3,
8497
- reconnectionDelayMax: 3e4,
8619
+ reconnectionAttempts: Infinity,
8620
+ // as connect(): never give up silently
8621
+ reconnectionDelay: RECONNECTION_DELAY,
8622
+ reconnectionDelayMax: RECONNECTION_DELAY_MAX,
8498
8623
  randomizationFactor: 0.5,
8499
8624
  // Connection timeout
8500
8625
  timeout: 2e4,
@@ -8509,6 +8634,7 @@
8509
8634
  channel,
8510
8635
  listeners: {}
8511
8636
  };
8637
+ spreadReconnectAfterServerClose(socket);
8512
8638
  return new Promise((resolve, reject) => {
8513
8639
  socket.on("connect", () => {
8514
8640
  _log(`Connected to public channel: ${channel}`);
@@ -8519,12 +8645,6 @@
8519
8645
  _log(`Connection error for public channel ${channel}:`, error);
8520
8646
  reject(error);
8521
8647
  });
8522
- socket.on("reconnect_attempt", (attempt) => {
8523
- _log(`Reconnection attempt ${attempt} for public channel: ${channel}`);
8524
- });
8525
- socket.on("reconnect", (attempt) => {
8526
- _log(`Reconnected to public channel ${channel} after ${attempt} attempts`);
8527
- });
8528
8648
  socket.on("reconnect_failed", () => {
8529
8649
  _log(`Reconnection failed for public channel ${channel} after maximum attempts`);
8530
8650
  const eventListeners = socketChannel.listeners["reconnect_failed"] || /* @__PURE__ */ new Set();
@@ -10331,6 +10451,10 @@
10331
10451
  /**
10332
10452
  * Revoke the bot's current API key and issue a new one.
10333
10453
  * The new raw key is returned once.
10454
+ *
10455
+ * The replacement carries the old key's scopes verbatim, so this is not a
10456
+ * way to pick up scopes added to the registry since the key was minted —
10457
+ * `updateBotKeyScopes` is.
10334
10458
  */
10335
10459
  async rotateBotKey(workspaceId, botId) {
10336
10460
  if (!workspaceId) throw new Error("workspaceId is required.");
@@ -10341,6 +10465,65 @@
10341
10465
  sendJWT: true
10342
10466
  });
10343
10467
  },
10468
+ /**
10469
+ * Mint an ADDITIONAL API key for the bot, up to 5 live keys.
10470
+ *
10471
+ * Unlike `rotateBotKey`, the bot's existing keys keep working — which is
10472
+ * what makes a zero-downtime changeover possible: mint, move traffic over,
10473
+ * then revoke the old key. The raw key is returned once.
10474
+ */
10475
+ async createBotKey(workspaceId, botId, keyData) {
10476
+ if (!workspaceId) throw new Error("workspaceId is required.");
10477
+ if (!botId) throw new Error("botId is required.");
10478
+ if (!keyData?.name) throw new Error("name is required.");
10479
+ return client._request({
10480
+ method: "POST",
10481
+ endpoint: `/v1/workspaces/${workspaceId}/bots/${botId}/keys`,
10482
+ body: keyData,
10483
+ sendJWT: true
10484
+ });
10485
+ },
10486
+ /**
10487
+ * Replace the scopes on one of the bot's API keys, in place.
10488
+ *
10489
+ * A key's scopes are a snapshot taken when it was minted, and the registry
10490
+ * grows — a key created before a scope existed cannot hold it. Rotation is
10491
+ * not a substitute: `rotateBotKey` copies the old scope set onto the
10492
+ * replacement, so a stale key stays stale through every rotation.
10493
+ *
10494
+ * This matters most for keys minted with `scopes` omitted, which means
10495
+ * "the full registry" as it stood that day, not "whatever the full registry
10496
+ * becomes".
10497
+ *
10498
+ * The secret is unchanged — every caller configured with this key keeps
10499
+ * working, which is the whole point. Requires `canManageBots`.
10500
+ */
10501
+ async updateBotKeyScopes(workspaceId, botId, keyId, data) {
10502
+ if (!workspaceId) throw new Error("workspaceId is required.");
10503
+ if (!botId) throw new Error("botId is required.");
10504
+ if (!keyId) throw new Error("keyId is required.");
10505
+ return client._request({
10506
+ method: "PATCH",
10507
+ endpoint: `/v1/workspaces/${workspaceId}/bots/${botId}/keys/${keyId}`,
10508
+ body: data,
10509
+ sendJWT: true
10510
+ });
10511
+ },
10512
+ /**
10513
+ * Revoke one of the bot's API keys, leaving its others working.
10514
+ * A revoked key stops authenticating immediately and frees a slot
10515
+ * against the 5-key cap.
10516
+ */
10517
+ async revokeBotKey(workspaceId, botId, keyId) {
10518
+ if (!workspaceId) throw new Error("workspaceId is required.");
10519
+ if (!botId) throw new Error("botId is required.");
10520
+ if (!keyId) throw new Error("keyId is required.");
10521
+ return client._request({
10522
+ method: "DELETE",
10523
+ endpoint: `/v1/workspaces/${workspaceId}/bots/${botId}/keys/${keyId}`,
10524
+ sendJWT: true
10525
+ });
10526
+ },
10344
10527
  /**
10345
10528
  * Request signed URL data to upload a new avatar for the bot.
10346
10529
  * Caller multipart-uploads the file to S3 using the returned `signedUrlData`,
@@ -10782,6 +10965,27 @@
10782
10965
  bypassCache: true
10783
10966
  });
10784
10967
  },
10968
+ /**
10969
+ * Replace the scopes on one of the caller's Personal Access Tokens.
10970
+ *
10971
+ * `scopes` is the complete new set, not a delta. This exists because
10972
+ * the scope registry grows over time: a token minted before a scope
10973
+ * existed cannot hold it, and without this the only remedy is minting
10974
+ * a replacement and reconfiguring everywhere the old one is used.
10975
+ *
10976
+ * The change takes effect on the token's very next request. Scopes
10977
+ * remain a ceiling, never a floor — widening a token never lets it do
10978
+ * anything its owner cannot already do.
10979
+ */
10980
+ async updateTokenScopes(tokenId, data) {
10981
+ if (!tokenId) throw new Error("tokenId is required.");
10982
+ return client._request({
10983
+ method: "PATCH",
10984
+ endpoint: `/v1/tokens/${tokenId}`,
10985
+ body: data,
10986
+ sendJWT: true
10987
+ });
10988
+ },
10785
10989
  /**
10786
10990
  * Revoke one of the caller's Personal Access Tokens. The revocation
10787
10991
  * is immediate — the token will return 401 on the very next request.
@@ -10797,6 +11001,38 @@
10797
11001
  };
10798
11002
  }
10799
11003
 
11004
+ // src/routes/oauthGrant.ts
11005
+ function createOAuthGrantMethods(client) {
11006
+ return {
11007
+ /**
11008
+ * List the applications the caller has authorised, newest first.
11009
+ * Expired grants are omitted by the server.
11010
+ */
11011
+ async listGrants() {
11012
+ const res = await client._request({
11013
+ method: "GET",
11014
+ endpoint: "/v1/oauth-grants",
11015
+ sendJWT: true,
11016
+ bypassCache: true
11017
+ });
11018
+ return res.results;
11019
+ },
11020
+ /**
11021
+ * Withdraw an application's access: the grant and every token issued
11022
+ * under it. An access token already in flight stays valid until it
11023
+ * expires, so revocation is not instantaneous for a request under way.
11024
+ */
11025
+ async revokeGrant(grantId) {
11026
+ if (!grantId) throw new Error("grantId is required.");
11027
+ return client._request({
11028
+ method: "DELETE",
11029
+ endpoint: `/v1/oauth-grants/${grantId}`,
11030
+ sendJWT: true
11031
+ });
11032
+ }
11033
+ };
11034
+ }
11035
+
10800
11036
  // src/routes/webhook.ts
10801
11037
  function createWebhookMethods(client) {
10802
11038
  return {
@@ -10989,10 +11225,10 @@
10989
11225
 
10990
11226
  // src/version.ts
10991
11227
  var SDK_VERSION = {
10992
- version: "1.4.0",
10993
- buildTimestamp: "2026-10-03T16:23:07.694Z",
10994
- buildHash: "60ddbec0797c9d1bdf96aaf49b69dda5",
10995
- gitCommit: "c000da4",
11228
+ version: "1.4.1",
11229
+ buildTimestamp: "2026-10-10T06:58:40.258Z",
11230
+ buildHash: "f6505e2eb9725dc8d239bb401ced8f88",
11231
+ gitCommit: "1c00e18",
10996
11232
  dirty: false
10997
11233
  };
10998
11234
 
@@ -11048,6 +11284,7 @@
11048
11284
  this.invalidateCacheOnMutation = options.invalidateCacheOnMutation === void 0 ? true : options.invalidateCacheOnMutation;
11049
11285
  this._onUnauthorized = options.onUnauthorized;
11050
11286
  this._onMaintenance = options.onMaintenance;
11287
+ this._onTokensChanged = options.onTokensChanged;
11051
11288
  this._apiKey = options.apiKey || null;
11052
11289
  if (this.browserMode && !this._apiKey) {
11053
11290
  this._loadTokens();
@@ -11087,6 +11324,7 @@
11087
11324
  this.credits = createCreditsMethods(this);
11088
11325
  this.scratch = createScratchMethods(this);
11089
11326
  this.token = createTokenMethods(this);
11327
+ this.oauthGrant = createOAuthGrantMethods(this);
11090
11328
  this.webhook = createWebhookMethods(this);
11091
11329
  this.supportTicket = createSupportTicketMethods(this);
11092
11330
  }
@@ -11326,6 +11564,39 @@
11326
11564
  console.error("Failed to save tokens to localStorage:", e);
11327
11565
  }
11328
11566
  }
11567
+ if (this._onTokensChanged) {
11568
+ try {
11569
+ this._onTokensChanged(accessToken || refreshToken ? this.getSession() : null);
11570
+ } catch (e) {
11571
+ console.error("onTokensChanged handler threw:", e);
11572
+ }
11573
+ }
11574
+ }
11575
+ /**
11576
+ * Returns the current session (tokens and expiries), or null when signed out.
11577
+ * @public
11578
+ */
11579
+ getSession() {
11580
+ if (!this._accessToken && !this._refreshTokenValue) return null;
11581
+ return {
11582
+ accessToken: this._accessToken,
11583
+ refreshToken: this._refreshTokenValue,
11584
+ accessTokenExpiry: this._accessTokenExpiry,
11585
+ refreshTokenExpiry: this._refreshTokenExpiry
11586
+ };
11587
+ }
11588
+ /**
11589
+ * Restores a session previously captured through `onTokensChanged` /
11590
+ * `getSession()` — for hosts that persist tokens outside localStorage.
11591
+ * Does not fire `onTokensChanged`. A missing or expired access token is
11592
+ * refreshed on the next authenticated request.
11593
+ * @public
11594
+ */
11595
+ restoreSession(session) {
11596
+ this._accessToken = session?.accessToken ?? null;
11597
+ this._refreshTokenValue = session?.refreshToken ?? null;
11598
+ this._accessTokenExpiry = session?.accessTokenExpiry ?? null;
11599
+ this._refreshTokenExpiry = session?.refreshTokenExpiry ?? null;
11329
11600
  }
11330
11601
  /**
11331
11602
  * Retrieves the current access token.