@espressif/rainmaker-admin-sdk 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/cjs/entries/ESPRMAuth.cjs +1 -0
  3. package/dist/cjs/entries/ESPRMBase.cjs +1 -0
  4. package/dist/cjs/index.cjs +1 -0
  5. package/dist/cjs/methods/ESPRMAuth/LoginWithAuthorizationCode.cjs +54 -0
  6. package/dist/cjs/services/ESPRMAPIManager.cjs +29 -4
  7. package/dist/cjs/utils/constants.cjs +4 -0
  8. package/dist/cjs/utils/error/errorMessages.cjs +3 -0
  9. package/dist/cjs/utils/error/parser.cjs +3 -2
  10. package/dist/esm/entries/ESPRMAuth.js +1 -0
  11. package/dist/esm/entries/ESPRMBase.js +1 -0
  12. package/dist/esm/index.js +1 -0
  13. package/dist/esm/methods/ESPRMAuth/LoginWithAuthorizationCode.js +52 -0
  14. package/dist/esm/services/ESPRMAPIManager.js +29 -4
  15. package/dist/esm/utils/constants.js +4 -0
  16. package/dist/esm/utils/error/errorMessages.js +3 -0
  17. package/dist/esm/utils/error/parser.js +3 -2
  18. package/dist/types/methods/ESPRMAuth/LoginWithAuthorizationCode.d.ts +40 -0
  19. package/dist/types/methods/ESPRMAuth/index.d.ts +1 -0
  20. package/dist/types/services/ESPRMAPIManager.d.ts +5 -0
  21. package/dist/types/types/auth.d.ts +30 -0
  22. package/dist/types/types/config.d.ts +5 -0
  23. package/dist/types/utils/constants.d.ts +4 -0
  24. package/dist/types/utils/error/errorMessages.d.ts +3 -0
  25. package/dist/types/utils/error/parser.d.ts +4 -0
  26. package/dist/types-cjs/methods/ESPRMAuth/LoginWithAuthorizationCode.d.cts +40 -0
  27. package/dist/types-cjs/methods/ESPRMAuth/index.d.cts +1 -0
  28. package/dist/types-cjs/services/ESPRMAPIManager.d.cts +5 -0
  29. package/dist/types-cjs/types/auth.d.cts +30 -0
  30. package/dist/types-cjs/types/config.d.cts +5 -0
  31. package/dist/types-cjs/utils/constants.d.cts +4 -0
  32. package/dist/types-cjs/utils/error/errorMessages.d.cts +3 -0
  33. package/dist/types-cjs/utils/error/parser.d.cts +4 -0
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
5
5
 
6
+ ## [1.4.0] - 2026-10-07
7
+
8
+ Minor release that adds OAuth 2.0 authorization-code login, so dashboards can
9
+ complete social sign-in that uses `response_type=code` (WeChat on China
10
+ deployments) through the SDK instead of calling `/token` themselves. Existing
11
+ methods keep their signatures; the two behaviour changes below are fixes.
12
+
13
+ ### Added
14
+
15
+ - `ESPRMAuth.loginWithAuthorizationCode({ code, redirectUri, clientId })`
16
+ completes an OAuth 2.0 authorization-code login (`response_type=code`, used
17
+ by WeChat sign-in on China deployments). It sends a form-encoded
18
+ `POST {baseUrl}/token` and stores the returned tokens like `login()`. When
19
+ no refresh token is issued, an empty one is stored.
20
+ - `ESPRMRequestConfig.formData` sends a request body as
21
+ `application/x-www-form-urlencoded`.
22
+ - `ESPRMAPIManager.getRootUrl()` returns the configured `baseUrl` without the
23
+ API version segment.
24
+ - Types `AuthorizationCodeLoginParams`, `AuthorizationCodeTokenRequest` and
25
+ `OAuthTokenResponse`, and validation codes `MISSING_AUTHORIZATION_CODE`,
26
+ `MISSING_REDIRECT_URI` and `MISSING_OAUTH_CLIENT_ID`.
27
+
28
+ ### Changed
29
+
30
+ - `ESPAPIError` now reads RFC 6749 error bodies too. `errorCode` falls back to
31
+ `error`, and `description` falls back to `error_description`, when the
32
+ RainMaker `error_code` / `description` fields are absent.
33
+ - A trailing slash on `baseUrl` is now removed before the version segment is
34
+ appended. Before, it produced a double slash such as `https://host//v1`.
35
+
6
36
  ## [1.3.1] - 2026-10-07
7
37
 
8
38
  Patch release that corrects the OTA job list types in `ESPRMAdminOTAJob.getJob`
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  require('../methods/ESPRMAuth/Login.cjs');
9
+ require('../methods/ESPRMAuth/LoginWithAuthorizationCode.cjs');
9
10
  require('../methods/ESPRMAuth/Logout.cjs');
10
11
  require('../methods/ESPRMAuth/ForgotPassword.cjs');
11
12
  require('../methods/ESPRMAuth/ChangePassword.cjs');
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  require('../methods/ESPRMAuth/Login.cjs');
9
+ require('../methods/ESPRMAuth/LoginWithAuthorizationCode.cjs');
9
10
  require('../methods/ESPRMAuth/Logout.cjs');
10
11
  require('../methods/ESPRMAuth/ForgotPassword.cjs');
11
12
  require('../methods/ESPRMAuth/ChangePassword.cjs');
@@ -58,6 +58,7 @@ var ESPRMAdminMatter = require('./ESPRMAdminMatter.cjs');
58
58
  var ESPRMAdminNodeParams = require('./ESPRMAdminNodeParams.cjs');
59
59
  var ESPRMClaim = require('./ESPRMClaim.cjs');
60
60
  require('./methods/ESPRMAuth/Login.cjs');
61
+ require('./methods/ESPRMAuth/LoginWithAuthorizationCode.cjs');
61
62
  require('./methods/ESPRMAuth/Logout.cjs');
62
63
  require('./methods/ESPRMAuth/ForgotPassword.cjs');
63
64
  require('./methods/ESPRMAuth/ChangePassword.cjs');
@@ -0,0 +1,54 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ 'use strict';
7
+
8
+ var ESPRMAuth = require('../../ESPRMAuth.cjs');
9
+ var ESPRMUser = require('../../ESPRMUser.cjs');
10
+ var ESPRMAPIManager = require('../../services/ESPRMAPIManager.cjs');
11
+ var constants = require('../../utils/constants.cjs');
12
+ var ESPValidationError = require('../../utils/error/ESPValidationError.cjs');
13
+ var parser = require('../../utils/error/parser.cjs');
14
+
15
+ function validateParams({ code, redirectUri, clientId, }) {
16
+ if (!code) {
17
+ throw new ESPValidationError.ESPValidationError(constants.ValidationErrorCodes.MISSING_AUTHORIZATION_CODE);
18
+ }
19
+ if (!redirectUri) {
20
+ throw new ESPValidationError.ESPValidationError(constants.ValidationErrorCodes.MISSING_REDIRECT_URI);
21
+ }
22
+ if (!clientId) {
23
+ throw new ESPValidationError.ESPValidationError(constants.ValidationErrorCodes.MISSING_OAUTH_CLIENT_ID);
24
+ }
25
+ }
26
+ ESPRMAuth.ESPRMAuth.prototype.loginWithAuthorizationCode = async function (params) {
27
+ validateParams(params);
28
+ const tokenRequest = {
29
+ grant_type: "authorization_code",
30
+ code: params.code,
31
+ redirect_uri: params.redirectUri,
32
+ client_id: params.clientId,
33
+ };
34
+ const requestConfig = {
35
+ baseURL: ESPRMAPIManager.ESPRMAPIManager.getRootUrl(),
36
+ url: constants.APIEndpoints.OAUTH_TOKEN,
37
+ method: constants.HTTPMethods.POST,
38
+ formData: tokenRequest,
39
+ };
40
+ const responseData = await ESPRMAPIManager.ESPRMAPIManager.request(requestConfig);
41
+ if (!responseData?.access_token || !responseData?.id_token) {
42
+ throw parser.parseAPIErrorResponse({
43
+ status: "failure",
44
+ description: "Token response is missing access_token or id_token",
45
+ }, 200);
46
+ }
47
+ const userTokens = {
48
+ accessToken: responseData.access_token,
49
+ idToken: responseData.id_token,
50
+ refreshToken: responseData.refresh_token ?? "",
51
+ };
52
+ await ESPRMUser.ESPRMUser.storeTokens(userTokens);
53
+ return new ESPRMUser.ESPRMUser(userTokens);
54
+ };
@@ -20,11 +20,13 @@ var common = require('../types/common.cjs');
20
20
  class ESPRMAPIManager {
21
21
  static #instance;
22
22
  #baseUrl;
23
+ #rootUrl;
23
24
  #timeoutMs;
24
25
  #claimingBaseUrl;
25
26
  constructor(config) {
26
27
  const { baseUrl, version, timeoutMs, claimingBaseUrl } = config;
27
- this.#baseUrl = `${baseUrl}/${version}`;
28
+ this.#rootUrl = baseUrl.replace(/\/+$/, "");
29
+ this.#baseUrl = `${this.#rootUrl}/${version}`;
28
30
  this.#timeoutMs = timeoutMs ?? constants.DEFAULT_REQUEST_TIMEOUT_MS;
29
31
  this.#claimingBaseUrl = claimingBaseUrl?.replace(/\/+$/, "");
30
32
  }
@@ -37,6 +39,13 @@ class ESPRMAPIManager {
37
39
  }
38
40
  return ESPRMAPIManager.#instance;
39
41
  }
42
+ /**
43
+ * Configured `baseUrl` without the API version segment, for the endpoints
44
+ * served outside `/{version}`, such as the OAuth `/token` endpoint.
45
+ */
46
+ static getRootUrl() {
47
+ return ESPRMAPIManager.#getInstance().#rootUrl;
48
+ }
40
49
  /**
41
50
  * Base URL of the claiming service set through `claimingBaseUrl`.
42
51
  *
@@ -82,6 +91,21 @@ class ESPRMAPIManager {
82
91
  throw error;
83
92
  }
84
93
  }
94
+ static #serializeBody(requestConfig) {
95
+ if (requestConfig.formData) {
96
+ return {
97
+ contentType: "application/x-www-form-urlencoded",
98
+ body: new URLSearchParams(requestConfig.formData).toString(),
99
+ };
100
+ }
101
+ if (requestConfig.data) {
102
+ return {
103
+ contentType: "application/json",
104
+ body: JSON.stringify(requestConfig.data),
105
+ };
106
+ }
107
+ return {};
108
+ }
85
109
  /**
86
110
  * Send an unauthenticated request. Non-2xx responses and network/timeout
87
111
  * failures reject with an {@link ESPAPIError}.
@@ -102,18 +126,19 @@ class ESPRMAPIManager {
102
126
  requestUrl += `?${new URLSearchParams(queryEntries).toString()}`;
103
127
  }
104
128
  }
129
+ const { contentType, body } = ESPRMAPIManager.#serializeBody(requestConfig);
105
130
  const fetchOptions = {
106
131
  method: requestConfig.method,
107
132
  // The API never redirects. Following one would re-send the body (login
108
133
  // password, refresh token) to whatever host the redirect names.
109
134
  redirect: "error",
110
135
  headers: {
111
- ...(requestConfig.data ? { "Content-Type": "application/json" } : {}),
136
+ ...(contentType ? { "Content-Type": contentType } : {}),
112
137
  ...requestConfig.headers,
113
138
  },
114
139
  };
115
- if (requestConfig.data) {
116
- fetchOptions.body = JSON.stringify(requestConfig.data);
140
+ if (body !== undefined) {
141
+ fetchOptions.body = body;
117
142
  }
118
143
  const timeoutMs = requestConfig.timeoutMs ?? instance.#timeoutMs;
119
144
  let timer;
@@ -17,6 +17,7 @@ const APIEndpoints = {
17
17
  LOGIN: "login2",
18
18
  PASSWORD: "password2",
19
19
  LOGOUT: "logout2",
20
+ OAUTH_TOKEN: "token",
20
21
  ADMIN_USER: "admin/user2",
21
22
  ADMIN_USER_SUMMARY: "admin/user2/summary",
22
23
  ADMIN_NODES: "admin/nodes",
@@ -115,6 +116,9 @@ const ValidationErrorCodes = {
115
116
  MISSING_LOGIN_PASSWORD: "MISSING_LOGIN_PASSWORD",
116
117
  MISSING_USERNAME: "MISSING_USERNAME",
117
118
  MISSING_VERIFICATION_CODE: "MISSING_VERIFICATION_CODE",
119
+ MISSING_AUTHORIZATION_CODE: "MISSING_AUTHORIZATION_CODE",
120
+ MISSING_REDIRECT_URI: "MISSING_REDIRECT_URI",
121
+ MISSING_OAUTH_CLIENT_ID: "MISSING_OAUTH_CLIENT_ID",
118
122
  };
119
123
  const StorageAdapterErrorCodes = {
120
124
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API",
@@ -16,6 +16,9 @@ const validationErrorMessages = {
16
16
  MISSING_LOGIN_PASSWORD: "Validation Error: Password is required.",
17
17
  MISSING_USERNAME: "Validation Error: Username is required.",
18
18
  MISSING_VERIFICATION_CODE: "Validation Error: Verification code is required.",
19
+ MISSING_AUTHORIZATION_CODE: "Validation Error: Authorization code is required.",
20
+ MISSING_REDIRECT_URI: "Validation Error: Redirect URI is required.",
21
+ MISSING_OAUTH_CLIENT_ID: "Validation Error: OAuth client ID is required.",
19
22
  };
20
23
  const storageAdapterErrorMessages = {
21
24
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "ESPStorageAdapterError: It seems like your environment doesn't support window.localstorage, you can define your own storage adapter while configuring the ESPRMBase instance. Please refer docs for more information.",
@@ -11,8 +11,9 @@ const parseAPIErrorResponse = (error, statusCode) => {
11
11
  let description = "An error has occurred";
12
12
  if (error) {
13
13
  status = error.status ?? "";
14
- errorCode = error.error_code ?? "";
15
- description = error.description ?? "An error has occurred";
14
+ errorCode = error.error_code ?? error.error ?? "";
15
+ description =
16
+ error.description ?? error.error_description ?? "An error has occurred";
16
17
  }
17
18
  else {
18
19
  throw error;
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import '../methods/ESPRMAuth/Login.js';
7
+ import '../methods/ESPRMAuth/LoginWithAuthorizationCode.js';
7
8
  import '../methods/ESPRMAuth/Logout.js';
8
9
  import '../methods/ESPRMAuth/ForgotPassword.js';
9
10
  import '../methods/ESPRMAuth/ChangePassword.js';
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import '../methods/ESPRMAuth/Login.js';
7
+ import '../methods/ESPRMAuth/LoginWithAuthorizationCode.js';
7
8
  import '../methods/ESPRMAuth/Logout.js';
8
9
  import '../methods/ESPRMAuth/ForgotPassword.js';
9
10
  import '../methods/ESPRMAuth/ChangePassword.js';
package/dist/esm/index.js CHANGED
@@ -56,6 +56,7 @@ export { ESPRMAdminMatter } from './ESPRMAdminMatter.js';
56
56
  export { ESPRMAdminNodeParams } from './ESPRMAdminNodeParams.js';
57
57
  export { ESPRMClaim } from './ESPRMClaim.js';
58
58
  import './methods/ESPRMAuth/Login.js';
59
+ import './methods/ESPRMAuth/LoginWithAuthorizationCode.js';
59
60
  import './methods/ESPRMAuth/Logout.js';
60
61
  import './methods/ESPRMAuth/ForgotPassword.js';
61
62
  import './methods/ESPRMAuth/ChangePassword.js';
@@ -0,0 +1,52 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { ESPRMAuth } from '../../ESPRMAuth.js';
7
+ import { ESPRMUser } from '../../ESPRMUser.js';
8
+ import { ESPRMAPIManager } from '../../services/ESPRMAPIManager.js';
9
+ import { HTTPMethods, APIEndpoints, ValidationErrorCodes } from '../../utils/constants.js';
10
+ import { ESPValidationError } from '../../utils/error/ESPValidationError.js';
11
+ import { parseAPIErrorResponse } from '../../utils/error/parser.js';
12
+
13
+ function validateParams({ code, redirectUri, clientId, }) {
14
+ if (!code) {
15
+ throw new ESPValidationError(ValidationErrorCodes.MISSING_AUTHORIZATION_CODE);
16
+ }
17
+ if (!redirectUri) {
18
+ throw new ESPValidationError(ValidationErrorCodes.MISSING_REDIRECT_URI);
19
+ }
20
+ if (!clientId) {
21
+ throw new ESPValidationError(ValidationErrorCodes.MISSING_OAUTH_CLIENT_ID);
22
+ }
23
+ }
24
+ ESPRMAuth.prototype.loginWithAuthorizationCode = async function (params) {
25
+ validateParams(params);
26
+ const tokenRequest = {
27
+ grant_type: "authorization_code",
28
+ code: params.code,
29
+ redirect_uri: params.redirectUri,
30
+ client_id: params.clientId,
31
+ };
32
+ const requestConfig = {
33
+ baseURL: ESPRMAPIManager.getRootUrl(),
34
+ url: APIEndpoints.OAUTH_TOKEN,
35
+ method: HTTPMethods.POST,
36
+ formData: tokenRequest,
37
+ };
38
+ const responseData = await ESPRMAPIManager.request(requestConfig);
39
+ if (!responseData?.access_token || !responseData?.id_token) {
40
+ throw parseAPIErrorResponse({
41
+ status: "failure",
42
+ description: "Token response is missing access_token or id_token",
43
+ }, 200);
44
+ }
45
+ const userTokens = {
46
+ accessToken: responseData.access_token,
47
+ idToken: responseData.id_token,
48
+ refreshToken: responseData.refresh_token ?? "",
49
+ };
50
+ await ESPRMUser.storeTokens(userTokens);
51
+ return new ESPRMUser(userTokens);
52
+ };
@@ -18,11 +18,13 @@ import { PropertyCheckMode } from '../types/common.js';
18
18
  class ESPRMAPIManager {
19
19
  static #instance;
20
20
  #baseUrl;
21
+ #rootUrl;
21
22
  #timeoutMs;
22
23
  #claimingBaseUrl;
23
24
  constructor(config) {
24
25
  const { baseUrl, version, timeoutMs, claimingBaseUrl } = config;
25
- this.#baseUrl = `${baseUrl}/${version}`;
26
+ this.#rootUrl = baseUrl.replace(/\/+$/, "");
27
+ this.#baseUrl = `${this.#rootUrl}/${version}`;
26
28
  this.#timeoutMs = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
27
29
  this.#claimingBaseUrl = claimingBaseUrl?.replace(/\/+$/, "");
28
30
  }
@@ -35,6 +37,13 @@ class ESPRMAPIManager {
35
37
  }
36
38
  return ESPRMAPIManager.#instance;
37
39
  }
40
+ /**
41
+ * Configured `baseUrl` without the API version segment, for the endpoints
42
+ * served outside `/{version}`, such as the OAuth `/token` endpoint.
43
+ */
44
+ static getRootUrl() {
45
+ return ESPRMAPIManager.#getInstance().#rootUrl;
46
+ }
38
47
  /**
39
48
  * Base URL of the claiming service set through `claimingBaseUrl`.
40
49
  *
@@ -80,6 +89,21 @@ class ESPRMAPIManager {
80
89
  throw error;
81
90
  }
82
91
  }
92
+ static #serializeBody(requestConfig) {
93
+ if (requestConfig.formData) {
94
+ return {
95
+ contentType: "application/x-www-form-urlencoded",
96
+ body: new URLSearchParams(requestConfig.formData).toString(),
97
+ };
98
+ }
99
+ if (requestConfig.data) {
100
+ return {
101
+ contentType: "application/json",
102
+ body: JSON.stringify(requestConfig.data),
103
+ };
104
+ }
105
+ return {};
106
+ }
83
107
  /**
84
108
  * Send an unauthenticated request. Non-2xx responses and network/timeout
85
109
  * failures reject with an {@link ESPAPIError}.
@@ -100,18 +124,19 @@ class ESPRMAPIManager {
100
124
  requestUrl += `?${new URLSearchParams(queryEntries).toString()}`;
101
125
  }
102
126
  }
127
+ const { contentType, body } = ESPRMAPIManager.#serializeBody(requestConfig);
103
128
  const fetchOptions = {
104
129
  method: requestConfig.method,
105
130
  // The API never redirects. Following one would re-send the body (login
106
131
  // password, refresh token) to whatever host the redirect names.
107
132
  redirect: "error",
108
133
  headers: {
109
- ...(requestConfig.data ? { "Content-Type": "application/json" } : {}),
134
+ ...(contentType ? { "Content-Type": contentType } : {}),
110
135
  ...requestConfig.headers,
111
136
  },
112
137
  };
113
- if (requestConfig.data) {
114
- fetchOptions.body = JSON.stringify(requestConfig.data);
138
+ if (body !== undefined) {
139
+ fetchOptions.body = body;
115
140
  }
116
141
  const timeoutMs = requestConfig.timeoutMs ?? instance.#timeoutMs;
117
142
  let timer;
@@ -15,6 +15,7 @@ const APIEndpoints = {
15
15
  LOGIN: "login2",
16
16
  PASSWORD: "password2",
17
17
  LOGOUT: "logout2",
18
+ OAUTH_TOKEN: "token",
18
19
  ADMIN_USER: "admin/user2",
19
20
  ADMIN_USER_SUMMARY: "admin/user2/summary",
20
21
  ADMIN_NODES: "admin/nodes",
@@ -113,6 +114,9 @@ const ValidationErrorCodes = {
113
114
  MISSING_LOGIN_PASSWORD: "MISSING_LOGIN_PASSWORD",
114
115
  MISSING_USERNAME: "MISSING_USERNAME",
115
116
  MISSING_VERIFICATION_CODE: "MISSING_VERIFICATION_CODE",
117
+ MISSING_AUTHORIZATION_CODE: "MISSING_AUTHORIZATION_CODE",
118
+ MISSING_REDIRECT_URI: "MISSING_REDIRECT_URI",
119
+ MISSING_OAUTH_CLIENT_ID: "MISSING_OAUTH_CLIENT_ID",
116
120
  };
117
121
  const StorageAdapterErrorCodes = {
118
122
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API",
@@ -14,6 +14,9 @@ const validationErrorMessages = {
14
14
  MISSING_LOGIN_PASSWORD: "Validation Error: Password is required.",
15
15
  MISSING_USERNAME: "Validation Error: Username is required.",
16
16
  MISSING_VERIFICATION_CODE: "Validation Error: Verification code is required.",
17
+ MISSING_AUTHORIZATION_CODE: "Validation Error: Authorization code is required.",
18
+ MISSING_REDIRECT_URI: "Validation Error: Redirect URI is required.",
19
+ MISSING_OAUTH_CLIENT_ID: "Validation Error: OAuth client ID is required.",
17
20
  };
18
21
  const storageAdapterErrorMessages = {
19
22
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "ESPStorageAdapterError: It seems like your environment doesn't support window.localstorage, you can define your own storage adapter while configuring the ESPRMBase instance. Please refer docs for more information.",
@@ -9,8 +9,9 @@ const parseAPIErrorResponse = (error, statusCode) => {
9
9
  let description = "An error has occurred";
10
10
  if (error) {
11
11
  status = error.status ?? "";
12
- errorCode = error.error_code ?? "";
13
- description = error.description ?? "An error has occurred";
12
+ errorCode = error.error_code ?? error.error ?? "";
13
+ description =
14
+ error.description ?? error.error_description ?? "An error has occurred";
14
15
  }
15
16
  else {
16
17
  throw error;
@@ -0,0 +1,40 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { ESPRMUser } from "../../ESPRMUser.js";
7
+ import { AuthorizationCodeLoginParams } from "../../types/auth.js";
8
+ declare module "../../ESPRMAuth.js" {
9
+ interface ESPRMAuth {
10
+ /**
11
+ * Complete an OAuth 2.0 authorization-code login (`response_type=code`)
12
+ * by exchanging the code returned on the redirect URI for tokens.
13
+ *
14
+ * Sends `POST {baseUrl}/token` (outside the API version path) as
15
+ * `application/x-www-form-urlencoded`, without an `Authorization` header.
16
+ * Stores the returned tokens like {@link ESPRMAuth.login}. When the server
17
+ * issues no refresh token, an empty one is stored, so the session ends
18
+ * when the access token expires.
19
+ *
20
+ * @param params - Authorization code plus the `redirect_uri` and
21
+ * `client_id` sent in the authorize request
22
+ * @returns A new {@link ESPRMUser} instance with active session tokens
23
+ * @throws ESPValidationError when `code`, `redirectUri` or `clientId` is empty
24
+ * @throws ESPAPIError on a non-2xx response, where `errorCode` carries the
25
+ * RFC 6749 `error` value (for example `invalid_grant`), or when a 2xx
26
+ * response lacks `access_token` or `id_token`
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const code = new URLSearchParams(window.location.search).get("code");
31
+ * const user = await ESPRMBase.getAuthInstance().loginWithAuthorizationCode({
32
+ * code,
33
+ * redirectUri: "https://dashboard.example.com/socialauth",
34
+ * clientId: "<OAUTH_CLIENT_ID>",
35
+ * });
36
+ * ```
37
+ */
38
+ loginWithAuthorizationCode(params: AuthorizationCodeLoginParams): Promise<ESPRMUser>;
39
+ }
40
+ }
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import "./Login.js";
7
+ import "./LoginWithAuthorizationCode.js";
7
8
  import "./Logout.js";
8
9
  import "./ForgotPassword.js";
9
10
  import "./ChangePassword.js";
@@ -13,6 +13,11 @@ export declare class ESPRMAPIManager {
13
13
  #private;
14
14
  private constructor();
15
15
  static initialize(config: ESPRMAPIManagerConfig): void;
16
+ /**
17
+ * Configured `baseUrl` without the API version segment, for the endpoints
18
+ * served outside `/{version}`, such as the OAuth `/token` endpoint.
19
+ */
20
+ static getRootUrl(): string;
16
21
  /**
17
22
  * Base URL of the claiming service set through `claimingBaseUrl`.
18
23
  *
@@ -84,3 +84,33 @@ export interface GetUserInfoResponse {
84
84
  locale?: string;
85
85
  tags?: string[];
86
86
  }
87
+ /** Parameters for {@link ESPRMAuth.loginWithAuthorizationCode}. */
88
+ export interface AuthorizationCodeLoginParams {
89
+ /** Authorization code from the `code` query parameter of the OAuth callback. */
90
+ code: string;
91
+ /** Redirect URI sent in the authorize request. Must match it exactly. */
92
+ redirectUri: string;
93
+ /** OAuth client ID sent in the authorize request. */
94
+ clientId: string;
95
+ }
96
+ /**
97
+ * Form body for `POST /token` with `grant_type=authorization_code`.
98
+ * A type alias, not an interface, so it is assignable to the
99
+ * `Record<string, string>` that `formData` takes.
100
+ */
101
+ export type AuthorizationCodeTokenRequest = {
102
+ grant_type: "authorization_code";
103
+ code: string;
104
+ redirect_uri: string;
105
+ client_id: string;
106
+ };
107
+ /** Successful `POST /token` response (RFC 6749 section 5.1). */
108
+ export interface OAuthTokenResponse {
109
+ access_token: string;
110
+ id_token: string;
111
+ /** Omitted or empty when the grant issues no refresh token. */
112
+ refresh_token?: string;
113
+ token_type?: string;
114
+ /** Access-token lifetime in seconds. */
115
+ expires_in?: number;
116
+ }
@@ -45,6 +45,11 @@ export interface ESPRMRequestConfig {
45
45
  method: string;
46
46
  /** Request body data (will be JSON-stringified). */
47
47
  data?: Record<string, unknown> | object;
48
+ /**
49
+ * Request body sent as `application/x-www-form-urlencoded`, for the OAuth
50
+ * endpoints. Takes precedence over `data` when both are set.
51
+ */
52
+ formData?: Record<string, string>;
48
53
  /** URL query parameters. */
49
54
  params?: Record<string, string> | object;
50
55
  /** Additional HTTP headers. */
@@ -15,6 +15,7 @@ declare const APIEndpoints: {
15
15
  readonly LOGIN: "login2";
16
16
  readonly PASSWORD: "password2";
17
17
  readonly LOGOUT: "logout2";
18
+ readonly OAUTH_TOKEN: "token";
18
19
  readonly ADMIN_USER: "admin/user2";
19
20
  readonly ADMIN_USER_SUMMARY: "admin/user2/summary";
20
21
  readonly ADMIN_NODES: "admin/nodes";
@@ -113,6 +114,9 @@ declare const ValidationErrorCodes: {
113
114
  readonly MISSING_LOGIN_PASSWORD: "MISSING_LOGIN_PASSWORD";
114
115
  readonly MISSING_USERNAME: "MISSING_USERNAME";
115
116
  readonly MISSING_VERIFICATION_CODE: "MISSING_VERIFICATION_CODE";
117
+ readonly MISSING_AUTHORIZATION_CODE: "MISSING_AUTHORIZATION_CODE";
118
+ readonly MISSING_REDIRECT_URI: "MISSING_REDIRECT_URI";
119
+ readonly MISSING_OAUTH_CLIENT_ID: "MISSING_OAUTH_CLIENT_ID";
116
120
  };
117
121
  declare const StorageAdapterErrorCodes: {
118
122
  readonly UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API";
@@ -14,6 +14,9 @@ declare const validationErrorMessages: {
14
14
  MISSING_LOGIN_PASSWORD: string;
15
15
  MISSING_USERNAME: string;
16
16
  MISSING_VERIFICATION_CODE: string;
17
+ MISSING_AUTHORIZATION_CODE: string;
18
+ MISSING_REDIRECT_URI: string;
19
+ MISSING_OAUTH_CLIENT_ID: string;
17
20
  };
18
21
  declare const storageAdapterErrorMessages: {
19
22
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: string;
@@ -8,6 +8,10 @@ interface RawAPIErrorBody {
8
8
  status?: string;
9
9
  error_code?: string;
10
10
  description?: string;
11
+ /** RFC 6749 error code, returned by the OAuth endpoints instead of `error_code`. */
12
+ error?: string;
13
+ /** RFC 6749 error text, returned by the OAuth endpoints instead of `description`. */
14
+ error_description?: string;
11
15
  }
12
16
  declare const parseAPIErrorResponse: (error: RawAPIErrorBody, statusCode: number) => ESPAPIError;
13
17
  export { parseAPIErrorResponse };
@@ -0,0 +1,40 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { ESPRMUser } from "../../ESPRMUser.cjs";
7
+ import { AuthorizationCodeLoginParams } from "../../types/auth.cjs";
8
+ declare module "../../ESPRMAuth.cjs" {
9
+ interface ESPRMAuth {
10
+ /**
11
+ * Complete an OAuth 2.0 authorization-code login (`response_type=code`)
12
+ * by exchanging the code returned on the redirect URI for tokens.
13
+ *
14
+ * Sends `POST {baseUrl}/token` (outside the API version path) as
15
+ * `application/x-www-form-urlencoded`, without an `Authorization` header.
16
+ * Stores the returned tokens like {@link ESPRMAuth.login}. When the server
17
+ * issues no refresh token, an empty one is stored, so the session ends
18
+ * when the access token expires.
19
+ *
20
+ * @param params - Authorization code plus the `redirect_uri` and
21
+ * `client_id` sent in the authorize request
22
+ * @returns A new {@link ESPRMUser} instance with active session tokens
23
+ * @throws ESPValidationError when `code`, `redirectUri` or `clientId` is empty
24
+ * @throws ESPAPIError on a non-2xx response, where `errorCode` carries the
25
+ * RFC 6749 `error` value (for example `invalid_grant`), or when a 2xx
26
+ * response lacks `access_token` or `id_token`
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const code = new URLSearchParams(window.location.search).get("code");
31
+ * const user = await ESPRMBase.getAuthInstance().loginWithAuthorizationCode({
32
+ * code,
33
+ * redirectUri: "https://dashboard.example.com/socialauth",
34
+ * clientId: "<OAUTH_CLIENT_ID>",
35
+ * });
36
+ * ```
37
+ */
38
+ loginWithAuthorizationCode(params: AuthorizationCodeLoginParams): Promise<ESPRMUser>;
39
+ }
40
+ }
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import "./Login.cjs";
7
+ import "./LoginWithAuthorizationCode.cjs";
7
8
  import "./Logout.cjs";
8
9
  import "./ForgotPassword.cjs";
9
10
  import "./ChangePassword.cjs";
@@ -13,6 +13,11 @@ export declare class ESPRMAPIManager {
13
13
  #private;
14
14
  private constructor();
15
15
  static initialize(config: ESPRMAPIManagerConfig): void;
16
+ /**
17
+ * Configured `baseUrl` without the API version segment, for the endpoints
18
+ * served outside `/{version}`, such as the OAuth `/token` endpoint.
19
+ */
20
+ static getRootUrl(): string;
16
21
  /**
17
22
  * Base URL of the claiming service set through `claimingBaseUrl`.
18
23
  *
@@ -84,3 +84,33 @@ export interface GetUserInfoResponse {
84
84
  locale?: string;
85
85
  tags?: string[];
86
86
  }
87
+ /** Parameters for {@link ESPRMAuth.loginWithAuthorizationCode}. */
88
+ export interface AuthorizationCodeLoginParams {
89
+ /** Authorization code from the `code` query parameter of the OAuth callback. */
90
+ code: string;
91
+ /** Redirect URI sent in the authorize request. Must match it exactly. */
92
+ redirectUri: string;
93
+ /** OAuth client ID sent in the authorize request. */
94
+ clientId: string;
95
+ }
96
+ /**
97
+ * Form body for `POST /token` with `grant_type=authorization_code`.
98
+ * A type alias, not an interface, so it is assignable to the
99
+ * `Record<string, string>` that `formData` takes.
100
+ */
101
+ export type AuthorizationCodeTokenRequest = {
102
+ grant_type: "authorization_code";
103
+ code: string;
104
+ redirect_uri: string;
105
+ client_id: string;
106
+ };
107
+ /** Successful `POST /token` response (RFC 6749 section 5.1). */
108
+ export interface OAuthTokenResponse {
109
+ access_token: string;
110
+ id_token: string;
111
+ /** Omitted or empty when the grant issues no refresh token. */
112
+ refresh_token?: string;
113
+ token_type?: string;
114
+ /** Access-token lifetime in seconds. */
115
+ expires_in?: number;
116
+ }
@@ -45,6 +45,11 @@ export interface ESPRMRequestConfig {
45
45
  method: string;
46
46
  /** Request body data (will be JSON-stringified). */
47
47
  data?: Record<string, unknown> | object;
48
+ /**
49
+ * Request body sent as `application/x-www-form-urlencoded`, for the OAuth
50
+ * endpoints. Takes precedence over `data` when both are set.
51
+ */
52
+ formData?: Record<string, string>;
48
53
  /** URL query parameters. */
49
54
  params?: Record<string, string> | object;
50
55
  /** Additional HTTP headers. */
@@ -15,6 +15,7 @@ declare const APIEndpoints: {
15
15
  readonly LOGIN: "login2";
16
16
  readonly PASSWORD: "password2";
17
17
  readonly LOGOUT: "logout2";
18
+ readonly OAUTH_TOKEN: "token";
18
19
  readonly ADMIN_USER: "admin/user2";
19
20
  readonly ADMIN_USER_SUMMARY: "admin/user2/summary";
20
21
  readonly ADMIN_NODES: "admin/nodes";
@@ -113,6 +114,9 @@ declare const ValidationErrorCodes: {
113
114
  readonly MISSING_LOGIN_PASSWORD: "MISSING_LOGIN_PASSWORD";
114
115
  readonly MISSING_USERNAME: "MISSING_USERNAME";
115
116
  readonly MISSING_VERIFICATION_CODE: "MISSING_VERIFICATION_CODE";
117
+ readonly MISSING_AUTHORIZATION_CODE: "MISSING_AUTHORIZATION_CODE";
118
+ readonly MISSING_REDIRECT_URI: "MISSING_REDIRECT_URI";
119
+ readonly MISSING_OAUTH_CLIENT_ID: "MISSING_OAUTH_CLIENT_ID";
116
120
  };
117
121
  declare const StorageAdapterErrorCodes: {
118
122
  readonly UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: "UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API";
@@ -14,6 +14,9 @@ declare const validationErrorMessages: {
14
14
  MISSING_LOGIN_PASSWORD: string;
15
15
  MISSING_USERNAME: string;
16
16
  MISSING_VERIFICATION_CODE: string;
17
+ MISSING_AUTHORIZATION_CODE: string;
18
+ MISSING_REDIRECT_URI: string;
19
+ MISSING_OAUTH_CLIENT_ID: string;
17
20
  };
18
21
  declare const storageAdapterErrorMessages: {
19
22
  UNSUPPORTED_DEFAULT_STORAGE_ADAPTER_API: string;
@@ -8,6 +8,10 @@ interface RawAPIErrorBody {
8
8
  status?: string;
9
9
  error_code?: string;
10
10
  description?: string;
11
+ /** RFC 6749 error code, returned by the OAuth endpoints instead of `error_code`. */
12
+ error?: string;
13
+ /** RFC 6749 error text, returned by the OAuth endpoints instead of `description`. */
14
+ error_description?: string;
11
15
  }
12
16
  declare const parseAPIErrorResponse: (error: RawAPIErrorBody, statusCode: number) => ESPAPIError;
13
17
  export { parseAPIErrorResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@espressif/rainmaker-admin-sdk",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Espressif's Rainmaker Admin SDK enables seamless integration of admin applications with the ESP Rainmaker ecosystem.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Espressif Systems (Shanghai) CO LTD",