@espressif/rainmaker-neo-base-sdk 1.0.0 → 1.1.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 (71) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +40 -4
  3. package/dist/cjs/ESPDevice.js +6 -0
  4. package/dist/cjs/ESPDevice.js.map +1 -1
  5. package/dist/cjs/ESPRMNeoNode.js +33 -14
  6. package/dist/cjs/ESPRMNeoNode.js.map +1 -1
  7. package/dist/cjs/index.js +3 -0
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/methods/ESPDevice/Provision.js +7 -0
  10. package/dist/cjs/methods/ESPDevice/Provision.js.map +1 -1
  11. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js +26 -0
  12. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  13. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js +46 -0
  14. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  15. package/dist/cjs/methods/ESPRMNeoNode/Sync.js +4 -1
  16. package/dist/cjs/methods/ESPRMNeoNode/Sync.js.map +1 -1
  17. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js +8 -9
  18. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  19. package/dist/cjs/proto/esp_rmaker_chal_resp.js +5 -0
  20. package/dist/cjs/proto/esp_rmaker_chal_resp.js.map +1 -1
  21. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  22. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  23. package/dist/cjs/services/NodeMQTTOrchestrator.js +2 -5
  24. package/dist/cjs/services/NodeMQTTOrchestrator.js.map +1 -1
  25. package/dist/cjs/utils/constants.js +10 -0
  26. package/dist/cjs/utils/constants.js.map +1 -1
  27. package/dist/cjs/utils/nodeUtils.js +12 -1
  28. package/dist/cjs/utils/nodeUtils.js.map +1 -1
  29. package/dist/cjs/utils/waitForNodeOnline.js +7 -6
  30. package/dist/cjs/utils/waitForNodeOnline.js.map +1 -1
  31. package/dist/esm/ESPDevice.js +6 -0
  32. package/dist/esm/ESPDevice.js.map +1 -1
  33. package/dist/esm/ESPRMNeoNode.js +33 -14
  34. package/dist/esm/ESPRMNeoNode.js.map +1 -1
  35. package/dist/esm/index.js +3 -1
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/esm/methods/ESPDevice/Provision.js +7 -0
  38. package/dist/esm/methods/ESPDevice/Provision.js.map +1 -1
  39. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js +24 -0
  40. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  41. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js +44 -0
  42. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  43. package/dist/esm/methods/ESPRMNeoNode/Sync.js +4 -1
  44. package/dist/esm/methods/ESPRMNeoNode/Sync.js.map +1 -1
  45. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js +9 -10
  46. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  47. package/dist/esm/proto/esp_rmaker_chal_resp.js +5 -0
  48. package/dist/esm/proto/esp_rmaker_chal_resp.js.map +1 -1
  49. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  50. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  51. package/dist/esm/services/NodeMQTTOrchestrator.js +2 -5
  52. package/dist/esm/services/NodeMQTTOrchestrator.js.map +1 -1
  53. package/dist/esm/utils/constants.js +10 -1
  54. package/dist/esm/utils/constants.js.map +1 -1
  55. package/dist/esm/utils/nodeUtils.js +12 -1
  56. package/dist/esm/utils/nodeUtils.js.map +1 -1
  57. package/dist/esm/utils/waitForNodeOnline.js +7 -6
  58. package/dist/esm/utils/waitForNodeOnline.js.map +1 -1
  59. package/dist/types/ESPDevice.d.ts +10 -0
  60. package/dist/types/ESPRMNeoNode.d.ts +17 -5
  61. package/dist/types/methods/ESPDevice/ResetWifiStatus.d.ts +18 -0
  62. package/dist/types/methods/ESPDevice/RetryNetworkCredentials.d.ts +22 -0
  63. package/dist/types/methods/ESPDevice/index.d.ts +2 -0
  64. package/dist/types/methods/ESPRMNeoUser/MQTT/AssumeRole.d.ts +25 -15
  65. package/dist/types/services/ESPRMNeoMqtt/ESPRMNeoMqtt.d.ts +10 -0
  66. package/dist/types/services/NodeMQTTOrchestrator.d.ts +2 -2
  67. package/dist/types/services/interfaces/MQTTTransport.d.ts +10 -0
  68. package/dist/types/types/provision.d.ts +10 -0
  69. package/dist/types/utils/constants.d.ts +10 -1
  70. package/dist/types/utils/waitForNodeOnline.d.ts +9 -3
  71. package/package.json +1 -1
@@ -38,14 +38,19 @@ class ESPRMNeoNode {
38
38
  subgroupIds = [];
39
39
  devices = [];
40
40
  services = [];
41
+ /**
42
+ * Last-known connectivity. Seeded from cached `connectivity_status` when
43
+ * present (cloud config does not provide it); otherwise starts offline until
44
+ * an MQTT shadow `online` update arrives.
45
+ */
41
46
  connectivityStatus = {
42
47
  isConnected: false,
43
48
  lastConnectionTimestamp: 0,
44
49
  };
45
50
  /**
46
51
  * Transports currently usable for this node, keyed by mode.
47
- * - `mqtt` (cloud) is added when the node reports online and removed when
48
- * it goes offline (via shadow updates or seeded `connectivity_status`).
52
+ * - `mqtt` (cloud) is added when the node is connected and removed when it
53
+ * goes offline (from cached `connectivity_status` or shadow updates).
49
54
  * - `local` is added/removed at runtime by a `localDiscovery` subscriber as the
50
55
  * node appears/disappears on the LAN (via {@link addTransport}/{@link removeTransport}).
51
56
  * Apps may also add custom string-keyed entries (pair with
@@ -78,16 +83,11 @@ class ESPRMNeoNode {
78
83
  this.subgroupIds = asStringArray(subgroupIdOrIds);
79
84
  this.transportOrder = ESPRMNeoBase.getTransportOrder();
80
85
  this.availableTransports = {};
81
- this.applyNodeConfig(config);
82
86
  if (config.connectivity_status) {
83
87
  this.connectivityStatus = config.connectivity_status;
84
88
  }
85
- if (this.connectivityStatus.isConnected) {
86
- this.addTransport(ESPTransportMode.mqtt, {
87
- type: ESPTransportMode.mqtt,
88
- metadata: {},
89
- });
90
- }
89
+ this.applyNodeConfig(config);
90
+ this.syncMqttTransportAvailability();
91
91
  this.subscribeToMqttUpdates();
92
92
  }
93
93
  /**
@@ -167,11 +167,14 @@ class ESPRMNeoNode {
167
167
  this.devices = deviceRecords.map((d) => new ESPRMNeoDevice(transformNodeDevice(d), nodeRef));
168
168
  this.services = serviceRecords.map((s) => new ESPRMNeoService(transformNodeService(s), nodeRef));
169
169
  // Keep wire records for cache writes (never persist live class instances).
170
+ // Preserve last-known connectivity when the incoming config omits it
171
+ // (cloud node config does not include connectivity_status).
170
172
  this.wireConfig = {
171
173
  ...config,
172
174
  info,
173
175
  devices: deviceRecords,
174
176
  services: serviceRecords,
177
+ connectivity_status: config.connectivity_status ?? this.connectivityStatus,
175
178
  };
176
179
  this.config = {
177
180
  ...config,
@@ -241,19 +244,27 @@ class ESPRMNeoNode {
241
244
  this.wireConfig.params = this.config.params;
242
245
  }
243
246
  this.applyConnectivityStatus(shadow, connectivityStatus);
244
- ESPRMNeoStorage.setItem(StorageKeys.NODE_CONFIG_PREFIX + this.nodeId, JSON.stringify({ ...this.wireConfig, node_id: this.nodeId }));
247
+ this.persistWireConfig();
245
248
  this.refreshConfigIfNcfgChanged(shadow);
246
249
  }
247
250
  /**
248
- * Updates {@link connectivityStatus} and MQTT transport availability when the
249
- * shadow reports `online`. Partial updates without `online` are ignored so
250
- * an existing status is not cleared.
251
+ * Updates {@link connectivityStatus} when the shadow reports `online`, then
252
+ * syncs MQTT transport availability. Partial updates without `online` are
253
+ * ignored so an existing status is not cleared.
251
254
  */
252
255
  applyConnectivityStatus(shadow, connectivityStatus) {
253
256
  if (shadow.state?.reported?.online === undefined)
254
257
  return;
255
258
  this.connectivityStatus = connectivityStatus;
256
- if (connectivityStatus.isConnected) {
259
+ this.wireConfig.connectivity_status = connectivityStatus;
260
+ this.syncMqttTransportAvailability();
261
+ }
262
+ /**
263
+ * Registers the MQTT transport only while {@link connectivityStatus} reports
264
+ * the node as connected; removes it when offline.
265
+ */
266
+ syncMqttTransportAvailability() {
267
+ if (this.connectivityStatus.isConnected) {
257
268
  this.addTransport(ESPTransportMode.mqtt, {
258
269
  type: ESPTransportMode.mqtt,
259
270
  metadata: {},
@@ -263,6 +274,14 @@ class ESPRMNeoNode {
263
274
  this.removeTransport(ESPTransportMode.mqtt);
264
275
  }
265
276
  }
277
+ /** Persists {@link wireConfig} (including last-known connectivity) to storage. */
278
+ persistWireConfig() {
279
+ ESPRMNeoStorage.setItem(StorageKeys.NODE_CONFIG_PREFIX + this.nodeId, JSON.stringify({
280
+ ...this.wireConfig,
281
+ node_id: this.nodeId,
282
+ connectivity_status: this.connectivityStatus,
283
+ }));
284
+ }
266
285
  /** When cloud `ncfg_ver` changes, refetch config and persist the new marker. */
267
286
  async refreshConfigIfNcfgChanged(shadow) {
268
287
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"ESPRMNeoNode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPRMNeoNode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/esm/index.js CHANGED
@@ -102,6 +102,8 @@ import './methods/ESPDevice/SetProofOfPossession.js';
102
102
  import './methods/ESPDevice/SendData.js';
103
103
  import './methods/ESPDevice/InitializeSession.js';
104
104
  import './methods/ESPDevice/SetNetworkCredentials.js';
105
+ import './methods/ESPDevice/ResetWifiStatus.js';
106
+ import './methods/ESPDevice/RetryNetworkCredentials.js';
105
107
  import './methods/ESPDevice/InitiateUserNodeMapping.js';
106
108
  import './methods/ESPDevice/VerifyUserNodeMapping.js';
107
109
  import './methods/ESPDevice/StartAssistedClaiming.js';
@@ -109,7 +111,7 @@ export { buildGroupControlParamsTopic, buildRainMakerUserParamsTopic, buildShado
109
111
  export { coerceToFiniteNumber, coerceToNonEmptyString, coerceToString } from './utils/coerce.js';
110
112
  export { buildCanonicalQueryString, createAuthorizationHeader, generateIotDeviceGatewayMqttSignedUrl, generateSigV4AuthHeader } from './utils/awsSigv4Utils.js';
111
113
  export { buildMainApiUrl, buildUserCredentialsUrl, ensureValidIdToken, extractTemporaryCredentials, isExpiredTokenAuthError, parseCredentialsErrorMessage, postUserCredentials, refreshUserSession } from './utils/awsUtils.js';
112
- export { APICallValidationErrorCodes, APIEndpoints, APIOperations, APIPathV1, AWSCredentialsErrorMessages, AuthErrorCodes, AuthSuccessMessages, AutomationStatusValues, AutomationSuccessMessages, CLAIM_CSR_MAX_CHUNKS, CLAIM_DEVICE_CSR_KEYS, CLAIM_DEVICE_MAC_KEYS, CLAIM_MAC_HEX_LENGTHS, CLAIM_MAC_SEPARATORS, CLAIM_MAX_FRAGMENT_SIZE, ClaimCapabilities, ClaimCapabilityPolicies, ClaimEndpoints, ClaimErrorCodes, ClaimProgressMessages, ConfigErrorCodes, DEFAULT_REST_API_VERSION, ESPProvProgressMessages, ESPRMNeoErrorCodes, ESPRMNeoStorageKeys, ESPServiceParamType, ESPServiceType, Endpoint, ErrorLabels, GroupSuccessMessages, GroupUserAliases, HTTPMethods, IntegrationSuccessMessages, NodeSuccessMessages, NodeWarnMessages, ProtocolType, ProvErrorCodes, ProvisionType, SDK_VERSION, ScheduleErrorMessages, ScheduleSuccessMessages, ServiceType, SharingSuccessMessages, StatusMessage, StorageAdapterErrorCodes, StorageKeys, SubscriptionChannelIds, TokenErrorCodes, TriggerErrorMessages, TriggerSuccessMessages, ValidationErrorCodes } from './utils/constants.js';
114
+ export { APICallValidationErrorCodes, APIEndpoints, APIOperations, APIPathV1, AWSCredentialsErrorMessages, AssumeRoleErrorMessages, AuthErrorCodes, AuthSuccessMessages, AutomationStatusValues, AutomationSuccessMessages, CLAIM_CSR_MAX_CHUNKS, CLAIM_DEVICE_CSR_KEYS, CLAIM_DEVICE_MAC_KEYS, CLAIM_MAC_HEX_LENGTHS, CLAIM_MAC_SEPARATORS, CLAIM_MAX_FRAGMENT_SIZE, ClaimCapabilities, ClaimCapabilityPolicies, ClaimEndpoints, ClaimErrorCodes, ClaimProgressMessages, ConfigErrorCodes, DEFAULT_REST_API_VERSION, ESPProvProgressMessages, ESPRMNeoErrorCodes, ESPRMNeoStorageKeys, ESPServiceParamType, ESPServiceType, Endpoint, ErrorLabels, GroupSuccessMessages, GroupUserAliases, HTTPMethods, IntegrationSuccessMessages, NodeSuccessMessages, NodeWarnMessages, ProtocolType, ProvErrorCodes, ProvisionType, SDK_VERSION, ScheduleErrorMessages, ScheduleSuccessMessages, ServiceType, SharingSuccessMessages, StatusMessage, StorageAdapterErrorCodes, StorageKeys, SubscriptionChannelIds, TokenErrorCodes, TriggerErrorMessages, TriggerSuccessMessages, ValidationErrorCodes } from './utils/constants.js';
113
115
  export { LogLevel, Logger } from './utils/logger.js';
114
116
  export { concurrentFetchPool } from './utils/mapLimit.js';
115
117
  export { EventEmitter } from './utils/eventEmitter.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -65,6 +65,13 @@ async function runChallengeResponseFlow(device, ssid, passphrase, groupId, onPro
65
65
  challenge_response: signedChallenge,
66
66
  node_id: nodeId,
67
67
  });
68
+ // Association is verified and cannot be repeated; remember what step 6 needs
69
+ // so `retryNetworkCredentials` can resume from the Wi-Fi step.
70
+ device.provisionResumeState = {
71
+ nodeId: nodeId,
72
+ groupId,
73
+ options: options,
74
+ };
68
75
  // 5. SET NETWORK CREDENTIALS
69
76
  reportProgress(onProgress, ESPProvResponseStatus.onProgress, ESPProvProgressMessages.SETTING_NETWORK_CREDENTIALS);
70
77
  await device.setNetworkCredentials(ssid, passphrase);
@@ -1 +1 @@
1
- {"version":3,"file":"Provision.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"Provision.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,24 @@
1
+ import { ESPDevice } from '../../ESPDevice.js';
2
+ import { Logger } from '../../utils/logger.js';
3
+
4
+ /*
5
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
6
+ *
7
+ * SPDX-License-Identifier: Apache-2.0
8
+ */
9
+ const logger = new Logger("ResetWifiStatus");
10
+ ESPDevice.prototype.resetWifiStatus = async function () {
11
+ if (!this.ESPProvisionAdapter.resetWifiStatus) {
12
+ logger.warn(`Adapter has no resetWifiStatus; treating as not acknowledged for "${this.name}"`);
13
+ return false;
14
+ }
15
+ try {
16
+ const acknowledged = await this.ESPProvisionAdapter.resetWifiStatus(this.name);
17
+ return acknowledged;
18
+ }
19
+ catch (error) {
20
+ logger.error(`<- device "${this.name}": reset failed`, error);
21
+ throw error;
22
+ }
23
+ };
24
+ //# sourceMappingURL=ResetWifiStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetWifiStatus.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,44 @@
1
+ import { ESPDevice } from '../../ESPDevice.js';
2
+ import { ESPProvResponseStatus } from '../../types/provision.js';
3
+ import { ESPProvProgressMessages } from '../../utils/constants.js';
4
+ import { ESPProvError } from '../../utils/error/ESPProvError.js';
5
+ import { waitForNodeOnline, DEFAULT_NODE_ONLINE_TIMEOUT_MS } from '../../utils/waitForNodeOnline.js';
6
+ import { Logger } from '../../utils/logger.js';
7
+
8
+ /*
9
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
10
+ *
11
+ * SPDX-License-Identifier: Apache-2.0
12
+ */
13
+ const logger = new Logger("RetryNetworkCredentials");
14
+ ESPDevice.prototype.retryNetworkCredentials = async function (ssid, passphrase, onProgress) {
15
+ const resumeState = this.provisionResumeState;
16
+ if (!resumeState) {
17
+ logger.error(`Retry refused for "${this.name}": no association recorded, nothing to resume`);
18
+ throw new ESPProvError("FAILED_PROV");
19
+ }
20
+ onProgress({
21
+ status: ESPProvResponseStatus.onProgress,
22
+ description: ESPProvProgressMessages.SETTING_NETWORK_CREDENTIALS,
23
+ });
24
+ await this.setNetworkCredentials(ssid, passphrase);
25
+ const options = resumeState.options;
26
+ if (options?.waitForOnline && options.user) {
27
+ onProgress({
28
+ status: ESPProvResponseStatus.onProgress,
29
+ description: ESPProvProgressMessages.WAITING_FOR_ONLINE,
30
+ });
31
+ await waitForNodeOnline({
32
+ nodeId: resumeState.nodeId,
33
+ groupId: resumeState.groupId,
34
+ user: options.user,
35
+ timeoutMs: options.onlineTimeoutMs ?? DEFAULT_NODE_ONLINE_TIMEOUT_MS,
36
+ });
37
+ }
38
+ onProgress({
39
+ status: ESPProvResponseStatus.succeed,
40
+ description: resumeState.nodeId ?? ESPProvProgressMessages.DEVICE_PROVISIONED,
41
+ });
42
+ return resumeState.nodeId;
43
+ };
44
+ //# sourceMappingURL=RetryNetworkCredentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetryNetworkCredentials.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -14,7 +14,10 @@ ESPRMNeoNode.prototype.sync = async function () {
14
14
  // Apply first so a schema issue surfaces to the caller instead of
15
15
  // poisoning the storage cache for the next cold start.
16
16
  this.applyNodeConfig(config);
17
- await writeLocalNodeConfig(this.nodeId, config);
17
+ await writeLocalNodeConfig(this.nodeId, {
18
+ ...config,
19
+ connectivity_status: this.connectivityStatus,
20
+ });
18
21
  return this;
19
22
  }
20
23
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"Sync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"Sync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { ESPRMNeoUser } from '../../../ESPRMNeoUser.js';
2
- import { APIPathV1 } from '../../../utils/constants.js';
2
+ import { AssumeRoleErrorMessages, APIPathV1 } from '../../../utils/constants.js';
3
3
  import { ESPSigV4APIManager } from '../../../services/ESPSigV4APIManager.js';
4
4
 
5
5
  /*
@@ -9,27 +9,26 @@ import { ESPSigV4APIManager } from '../../../services/ESPSigV4APIManager.js';
9
9
  */
10
10
  /**
11
11
  * Implementation of the `assumeRole` method for the `ESPRMNeoUser` class.
12
- *
13
- * Exchanges identity (temporary) credentials for assume-role credentials with IoT/MQTT
14
- * permissions. The backend signs the user in with the provided credentials and returns
15
- * a new credential set for MQTT; this is not refreshing or extending the same credentials.
16
12
  */
17
13
  ESPRMNeoUser.prototype.assumeRole = async function (accessKey, secretKey, sessionToken, options) {
18
- // Request body: identity (temporary) credentials so backend can assume the IoT role
19
14
  const requestBody = {
20
15
  access_key: accessKey,
21
16
  secret_key: secretKey,
22
17
  session_token: sessionToken,
23
18
  };
24
- // Merge optional fields (e.g., include: ["s3", "kvs"])
25
- if (options?.include && options.include.length > 0) {
26
- requestBody.include = options.include;
19
+ const servicesMode = !!(options?.services && options.services.length > 0);
20
+ if (servicesMode) {
21
+ if (!options.nodeId) {
22
+ throw new Error(AssumeRoleErrorMessages.NODE_ID_REQUIRED);
23
+ }
24
+ requestBody.services = options.services;
25
+ requestBody.node_id = options.nodeId;
27
26
  }
28
27
  const api = ESPSigV4APIManager.getInstance();
29
28
  const response = await api.post(APIPathV1.ASSUME_ROLE, requestBody);
30
29
  // Validate response contains required fields
31
30
  if (!response.access_key || !response.secret_key || !response.session_token) {
32
- throw new Error("Invalid assume role response: missing required credential fields");
31
+ throw new Error(AssumeRoleErrorMessages.INVALID_RESPONSE);
33
32
  }
34
33
  return response;
35
34
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AssumeRole.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"AssumeRole.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,10 @@
1
1
  import * as pb_1 from 'google-protobuf';
2
2
 
3
+ /*
4
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
5
+ *
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
3
8
  /**
4
9
  * Generated by the protoc-gen-ts. DO NOT EDIT!
5
10
  * compiler version: 5.29.3
@@ -1 +1 @@
1
- {"version":3,"file":"esp_rmaker_chal_resp.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"esp_rmaker_chal_resp.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -85,6 +85,19 @@ class ESPRMNeoMqtt {
85
85
  isConnected() {
86
86
  return this.#adapter.isConnected();
87
87
  }
88
+ /**
89
+ * Registers a transport connection-status listener when the underlying
90
+ * adapter supports it; otherwise returns a no-op unsubscribe.
91
+ *
92
+ * @param callback - Invoked with `{ connected }` on status changes
93
+ * @returns Unsubscribe function
94
+ */
95
+ onConnectionStatusChange(callback) {
96
+ if (typeof this.#adapter.onConnectionStatusChange === "function") {
97
+ return this.#adapter.onConnectionStatusChange(callback);
98
+ }
99
+ return () => { };
100
+ }
88
101
  /**
89
102
  * Publish a message to a topic.
90
103
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ESPRMNeoMqtt.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPRMNeoMqtt.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -139,8 +139,8 @@ class NodeMQTTOrchestrator {
139
139
  /**
140
140
  * Registers a node with the orchestrator.
141
141
  * Must be called before subscribeToNode, getParams, getShadow, etc.
142
- * On shadow rename: clears the old binding; ignores shorter/incomplete names.
143
- * Shrink membership via {@link unregisterNode} first.
142
+ * On shadow rename: clears the old binding, then installs the new name
143
+ * (including shrinks to a shorter membership shadow).
144
144
  *
145
145
  * @param nodeId - The device/node identifier.
146
146
  * @param shadowName - The named shadow (e.g. `params-groupId-subgroupId`).
@@ -149,9 +149,6 @@ class NodeMQTTOrchestrator {
149
149
  const instance = NodeMQTTOrchestrator.getInstance();
150
150
  const existing = instance.nodeMap.get(nodeId);
151
151
  if (existing && existing.shadowName !== shadowName) {
152
- if (shadowName.split("-").length < existing.shadowName.split("-").length) {
153
- return;
154
- }
155
152
  NodeMQTTOrchestrator.unregisterNode(nodeId);
156
153
  }
157
154
  instance.nodeMap.set(nodeId, { shadowName });
@@ -1 +1 @@
1
- {"version":3,"file":"NodeMQTTOrchestrator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"NodeMQTTOrchestrator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -391,6 +391,15 @@ const TriggerErrorMessages = {
391
391
  /** Logged when retrieving triggers for a node fails. */
392
392
  GET_FAILED: "getTriggers failed",
393
393
  };
394
+ /**
395
+ * Error messages for assumeRole (POST /v1/assumed-roles).
396
+ */
397
+ const AssumeRoleErrorMessages = {
398
+ /** Thrown when `services` is provided without a `nodeId`. */
399
+ NODE_ID_REQUIRED: "assumeRole: nodeId is required when services is provided",
400
+ /** Thrown when the assume-role response is missing required credential fields. */
401
+ INVALID_RESPONSE: "Invalid assume role response: missing required credential fields",
402
+ };
394
403
  /**
395
404
  * Error / log messages for temporary AWS credentials (POST /v1/user/credentials).
396
405
  */
@@ -840,5 +849,5 @@ const SubscriptionChannelIds = {
840
849
  MQTT: "mqtt",
841
850
  };
842
851
 
843
- export { APICallValidationErrorCodes, APIEndpoints, APIOperations, APIPathV1, AWSCredentialsErrorMessages, AuthErrorCodes, AuthSuccessMessages, AutomationStatusValues, AutomationSuccessMessages, CLAIM_CSR_MAX_CHUNKS, CLAIM_DEVICE_CSR_KEYS, CLAIM_DEVICE_MAC_KEYS, CLAIM_MAC_HEX_LENGTHS, CLAIM_MAC_SEPARATORS, CLAIM_MAX_FRAGMENT_SIZE, ClaimCapabilities, ClaimCapabilityPolicies, ClaimEndpoints, ClaimErrorCodes, ClaimProgressMessages, ConfigErrorCodes, DEFAULT_REST_API_VERSION, ESPProvProgressMessages, ESPRMNeoErrorCodes, ESPRMNeoStorageKeys, ESPServiceParamType, ESPServiceType, Endpoint, ErrorLabels, GroupSuccessMessages, GroupUserAliases, HTTPMethods, IntegrationSuccessMessages, NodeSuccessMessages, NodeWarnMessages, ProtocolType, ProvErrorCodes, ProvisionType, SDK_VERSION, ScheduleErrorMessages, ScheduleSuccessMessages, ServiceType, SharingSuccessMessages, StatusMessage, StorageAdapterErrorCodes, StorageKeys, SubscriptionChannelIds, TokenErrorCodes, TriggerErrorMessages, TriggerSuccessMessages, ValidationErrorCodes };
852
+ export { APICallValidationErrorCodes, APIEndpoints, APIOperations, APIPathV1, AWSCredentialsErrorMessages, AssumeRoleErrorMessages, AuthErrorCodes, AuthSuccessMessages, AutomationStatusValues, AutomationSuccessMessages, CLAIM_CSR_MAX_CHUNKS, CLAIM_DEVICE_CSR_KEYS, CLAIM_DEVICE_MAC_KEYS, CLAIM_MAC_HEX_LENGTHS, CLAIM_MAC_SEPARATORS, CLAIM_MAX_FRAGMENT_SIZE, ClaimCapabilities, ClaimCapabilityPolicies, ClaimEndpoints, ClaimErrorCodes, ClaimProgressMessages, ConfigErrorCodes, DEFAULT_REST_API_VERSION, ESPProvProgressMessages, ESPRMNeoErrorCodes, ESPRMNeoStorageKeys, ESPServiceParamType, ESPServiceType, Endpoint, ErrorLabels, GroupSuccessMessages, GroupUserAliases, HTTPMethods, IntegrationSuccessMessages, NodeSuccessMessages, NodeWarnMessages, ProtocolType, ProvErrorCodes, ProvisionType, SDK_VERSION, ScheduleErrorMessages, ScheduleSuccessMessages, ServiceType, SharingSuccessMessages, StatusMessage, StorageAdapterErrorCodes, StorageKeys, SubscriptionChannelIds, TokenErrorCodes, TriggerErrorMessages, TriggerSuccessMessages, ValidationErrorCodes };
844
853
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"constants.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -26,7 +26,18 @@ async function readLocalNodeConfig(nodeId) {
26
26
  /** Best-effort persist of node config to local storage. */
27
27
  async function writeLocalNodeConfig(nodeId, config) {
28
28
  try {
29
- await ESPRMNeoStorage.setNodeConfig(nodeId, config);
29
+ let toStore = config;
30
+ // Cloud config omits connectivity — keep the last cached status when writing.
31
+ if (!config.connectivity_status) {
32
+ const prev = await ESPRMNeoStorage.getNodeConfig(nodeId);
33
+ if (prev?.connectivity_status) {
34
+ toStore = {
35
+ ...config,
36
+ connectivity_status: prev.connectivity_status,
37
+ };
38
+ }
39
+ }
40
+ await ESPRMNeoStorage.setNodeConfig(nodeId, toStore);
30
41
  }
31
42
  catch (error) {
32
43
  logger.warn(`Cache write failed for ${nodeId}`, error);
@@ -1 +1 @@
1
- {"version":3,"file":"nodeUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"nodeUtils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -63,11 +63,12 @@ function isNodeOnlineFromShadowPayload(payload) {
63
63
  * @throws {ESPProvError} If `timeoutMs` elapses without the node reporting online.
64
64
  * @throws {Error} If MQTT connection or subscription fails.
65
65
  */
66
- async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
67
- const shadowName = constructShadowName(groupId);
66
+ async function waitForNodeOnline({ nodeId, groupId, subgroupIds, user, timeoutMs = DEFAULT_NODE_ONLINE_TIMEOUT_MS, pollIntervalMs = DEFAULT_NODE_ONLINE_POLL_INTERVAL_MS, }) {
67
+ const shadowName = constructShadowName(groupId, subgroupIds ?? []);
68
68
  logger.info("Waiting for node to come online", {
69
69
  nodeId,
70
70
  groupId,
71
+ subgroupIds: subgroupIds ?? [],
71
72
  shadowName,
72
73
  timeoutMs,
73
74
  pollIntervalMs,
@@ -147,10 +148,10 @@ async function waitForNodeOnline({ nodeId, groupId, user, timeoutMs = DEFAULT_NO
147
148
  });
148
149
  await user.connectMQTT();
149
150
  }
150
- // 2. Register (if needed) and subscribe before the shadow may exist
151
- if (!alreadyRegistered) {
152
- NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
153
- }
151
+ // 2. Bind to the membership shadow and subscribe before the shadow may exist.
152
+ // Always registerNode so a leftover longer/stale binding for the same nodeId
153
+ // is rebound to constructShadowName(groupId, subgroupIds).
154
+ NodeMQTTOrchestrator.registerNode(nodeId, shadowName);
154
155
  await NodeMQTTOrchestrator.subscribeToNode(nodeId, onUpdate);
155
156
  logger.debug("Subscribed to node shadow for online wait", {
156
157
  nodeId,
@@ -1 +1 @@
1
- {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"waitForNodeOnline.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -9,5 +9,15 @@ export declare class ESPDevice implements ESPDeviceInterface {
9
9
  name: string;
10
10
  transport: string;
11
11
  security: number;
12
+ /**
13
+ * What a completed association produced, kept so `retryNetworkCredentials`
14
+ * can resume the flow instead of repeating it. Set once verification
15
+ * succeeds; `undefined` until then.
16
+ */
17
+ provisionResumeState?: {
18
+ nodeId: string;
19
+ groupId: string;
20
+ options?: Record<string, any>;
21
+ };
12
22
  constructor(deviceConfig: ESPDeviceInterface);
13
23
  }
@@ -22,11 +22,16 @@ export declare class ESPRMNeoNode {
22
22
  subgroupIds: string[];
23
23
  devices: ESPRMNeoDevice[];
24
24
  services: ESPRMNeoService[];
25
+ /**
26
+ * Last-known connectivity. Seeded from cached `connectivity_status` when
27
+ * present (cloud config does not provide it); otherwise starts offline until
28
+ * an MQTT shadow `online` update arrives.
29
+ */
25
30
  connectivityStatus: ESPRMNeoConnectivityStatusInterface;
26
31
  /**
27
32
  * Transports currently usable for this node, keyed by mode.
28
- * - `mqtt` (cloud) is added when the node reports online and removed when
29
- * it goes offline (via shadow updates or seeded `connectivity_status`).
33
+ * - `mqtt` (cloud) is added when the node is connected and removed when it
34
+ * goes offline (from cached `connectivity_status` or shadow updates).
30
35
  * - `local` is added/removed at runtime by a `localDiscovery` subscriber as the
31
36
  * node appears/disappears on the LAN (via {@link addTransport}/{@link removeTransport}).
32
37
  * Apps may also add custom string-keyed entries (pair with
@@ -132,11 +137,18 @@ export declare class ESPRMNeoNode {
132
137
  */
133
138
  private processNodeUpdate;
134
139
  /**
135
- * Updates {@link connectivityStatus} and MQTT transport availability when the
136
- * shadow reports `online`. Partial updates without `online` are ignored so
137
- * an existing status is not cleared.
140
+ * Updates {@link connectivityStatus} when the shadow reports `online`, then
141
+ * syncs MQTT transport availability. Partial updates without `online` are
142
+ * ignored so an existing status is not cleared.
138
143
  */
139
144
  private applyConnectivityStatus;
145
+ /**
146
+ * Registers the MQTT transport only while {@link connectivityStatus} reports
147
+ * the node as connected; removes it when offline.
148
+ */
149
+ private syncMqttTransportAvailability;
150
+ /** Persists {@link wireConfig} (including last-known connectivity) to storage. */
151
+ private persistWireConfig;
140
152
  /** When cloud `ncfg_ver` changes, refetch config and persist the new marker. */
141
153
  private refreshConfigIfNcfgChanged;
142
154
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Augments the ESPDevice class with the `resetWifiStatus` method.
3
+ */
4
+ declare module "../../ESPDevice" {
5
+ interface ESPDevice {
6
+ /**
7
+ * Asks the device to clear its Wi-Fi state over the open provisioning session.
8
+ *
9
+ * Sent on the network-control endpoint, so it needs an established session and
10
+ * leaves the user-node association intact — only Wi-Fi credentials are cleared.
11
+ * That is what makes `retryNetworkCredentials` possible after a wrong password,
12
+ * instead of making the user factory reset the board.
13
+ * @returns A promise resolving to `true` when the device acknowledged the reset.
14
+ */
15
+ resetWifiStatus(): Promise<boolean>;
16
+ }
17
+ }
18
+ export {};
@@ -0,0 +1,22 @@
1
+ import { ESPProvResponse } from "../../types/provision";
2
+ /**
3
+ * Augments the ESPDevice class with the `retryNetworkCredentials` method.
4
+ */
5
+ declare module "../../ESPDevice" {
6
+ interface ESPDevice {
7
+ /**
8
+ * Re-sends Wi-Fi credentials after `resetWifiStatus`, resuming the flow the
9
+ * first attempt started rather than repeating its association.
10
+ *
11
+ * Sending credentials alone is not enough: the online wait and the success
12
+ * report follow them and need the node id the association produced, which
13
+ * only this SDK holds.
14
+ * @param ssid - The SSID, unchanged from the first attempt.
15
+ * @param passphrase - The corrected Wi-Fi password.
16
+ * @param onProgress - Progress callback; emits the same messages as `provision`.
17
+ * @returns A promise resolving to the node id.
18
+ * @throws {ESPProvError} When no association has completed on this device yet.
19
+ */
20
+ retryNetworkCredentials(ssid: string, passphrase: string, onProgress: (message: ESPProvResponse) => void): Promise<string>;
21
+ }
22
+ }
@@ -8,6 +8,8 @@ import "./SetProofOfPossession";
8
8
  import "./SendData";
9
9
  import "./InitializeSession";
10
10
  import "./SetNetworkCredentials";
11
+ import "./ResetWifiStatus";
12
+ import "./RetryNetworkCredentials";
11
13
  import "./InitiateUserNodeMapping";
12
14
  import "./VerifyUserNodeMapping";
13
15
  import "./StartAssistedClaiming";