@espressif/rainmaker-neo-base-sdk 1.0.0 → 1.2.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 (117) 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/ESPRMNeoBase.js +1 -1
  6. package/dist/cjs/ESPRMNeoNode.js +33 -14
  7. package/dist/cjs/ESPRMNeoNode.js.map +1 -1
  8. package/dist/cjs/index.js +14 -0
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/methods/ESPDevice/Provision.js +7 -0
  11. package/dist/cjs/methods/ESPDevice/Provision.js.map +1 -1
  12. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js +26 -0
  13. package/dist/cjs/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  14. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js +46 -0
  15. package/dist/cjs/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  16. package/dist/cjs/methods/ESPRMNeoNode/Sync.js +4 -1
  17. package/dist/cjs/methods/ESPRMNeoNode/Sync.js.map +1 -1
  18. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js +8 -9
  19. package/dist/cjs/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  20. package/dist/cjs/proto/esp_rmaker_chal_resp.js +5 -0
  21. package/dist/cjs/proto/esp_rmaker_chal_resp.js.map +1 -1
  22. package/dist/cjs/proto/rmaker_local_ctrl.js +273 -0
  23. package/dist/cjs/proto/rmaker_local_ctrl.js.map +1 -0
  24. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  25. package/dist/cjs/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  26. package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
  27. package/dist/cjs/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
  28. package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js +183 -116
  29. package/dist/cjs/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
  30. package/dist/cjs/services/ESPTransport/LocalControlSession.js +62 -0
  31. package/dist/cjs/services/ESPTransport/LocalControlSession.js.map +1 -0
  32. package/dist/cjs/services/NodeMQTTOrchestrator.js +2 -5
  33. package/dist/cjs/services/NodeMQTTOrchestrator.js.map +1 -1
  34. package/dist/cjs/types/transport.js +24 -0
  35. package/dist/cjs/types/transport.js.map +1 -1
  36. package/dist/cjs/utils/constants.js +80 -4
  37. package/dist/cjs/utils/constants.js.map +1 -1
  38. package/dist/cjs/utils/eventSubscriptionUtils.js +48 -4
  39. package/dist/cjs/utils/eventSubscriptionUtils.js.map +1 -1
  40. package/dist/cjs/utils/nodeUtils.js +12 -1
  41. package/dist/cjs/utils/nodeUtils.js.map +1 -1
  42. package/dist/cjs/utils/waitForNodeOnline.js +7 -6
  43. package/dist/cjs/utils/waitForNodeOnline.js.map +1 -1
  44. package/dist/esm/ESPDevice.js +6 -0
  45. package/dist/esm/ESPDevice.js.map +1 -1
  46. package/dist/esm/ESPRMNeoBase.js +1 -1
  47. package/dist/esm/ESPRMNeoNode.js +33 -14
  48. package/dist/esm/ESPRMNeoNode.js.map +1 -1
  49. package/dist/esm/index.js +4 -2
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/methods/ESPDevice/Provision.js +7 -0
  52. package/dist/esm/methods/ESPDevice/Provision.js.map +1 -1
  53. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js +24 -0
  54. package/dist/esm/methods/ESPDevice/ResetWifiStatus.js.map +1 -0
  55. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js +44 -0
  56. package/dist/esm/methods/ESPDevice/RetryNetworkCredentials.js.map +1 -0
  57. package/dist/esm/methods/ESPRMNeoNode/Sync.js +4 -1
  58. package/dist/esm/methods/ESPRMNeoNode/Sync.js.map +1 -1
  59. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js +9 -10
  60. package/dist/esm/methods/ESPRMNeoUser/MQTT/AssumeRole.js.map +1 -1
  61. package/dist/esm/proto/esp_rmaker_chal_resp.js +5 -0
  62. package/dist/esm/proto/esp_rmaker_chal_resp.js.map +1 -1
  63. package/dist/esm/proto/rmaker_local_ctrl.js +271 -0
  64. package/dist/esm/proto/rmaker_local_ctrl.js.map +1 -0
  65. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js +13 -0
  66. package/dist/esm/services/ESPRMNeoMqtt/ESPRMNeoMqtt.js.map +1 -1
  67. package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js +5 -4
  68. package/dist/esm/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.js.map +1 -1
  69. package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js +184 -117
  70. package/dist/esm/services/ESPTransport/ESPLocalControlTransport.js.map +1 -1
  71. package/dist/esm/services/ESPTransport/LocalControlSession.js +59 -0
  72. package/dist/esm/services/ESPTransport/LocalControlSession.js.map +1 -0
  73. package/dist/esm/services/NodeMQTTOrchestrator.js +2 -5
  74. package/dist/esm/services/NodeMQTTOrchestrator.js.map +1 -1
  75. package/dist/esm/types/transport.js +24 -1
  76. package/dist/esm/types/transport.js.map +1 -1
  77. package/dist/esm/utils/constants.js +74 -5
  78. package/dist/esm/utils/constants.js.map +1 -1
  79. package/dist/esm/utils/eventSubscriptionUtils.js +49 -5
  80. package/dist/esm/utils/eventSubscriptionUtils.js.map +1 -1
  81. package/dist/esm/utils/nodeUtils.js +12 -1
  82. package/dist/esm/utils/nodeUtils.js.map +1 -1
  83. package/dist/esm/utils/waitForNodeOnline.js +7 -6
  84. package/dist/esm/utils/waitForNodeOnline.js.map +1 -1
  85. package/dist/types/ESPDevice.d.ts +10 -0
  86. package/dist/types/ESPRMNeoBase.d.ts +1 -1
  87. package/dist/types/ESPRMNeoNode.d.ts +17 -5
  88. package/dist/types/methods/ESPDevice/ResetWifiStatus.d.ts +18 -0
  89. package/dist/types/methods/ESPDevice/RetryNetworkCredentials.d.ts +22 -0
  90. package/dist/types/methods/ESPDevice/index.d.ts +2 -0
  91. package/dist/types/methods/ESPRMNeoUser/MQTT/AssumeRole.d.ts +25 -15
  92. package/dist/types/proto/rmaker_local_ctrl.d.ts +137 -0
  93. package/dist/types/services/ESPRMNeoMqtt/ESPRMNeoMqtt.d.ts +10 -0
  94. package/dist/types/services/ESPTransport/ESPDiscovery/ESPDiscoveryManager.d.ts +4 -3
  95. package/dist/types/services/ESPTransport/ESPLocalControlTransport.d.ts +70 -23
  96. package/dist/types/services/ESPTransport/LocalControlSession.d.ts +24 -0
  97. package/dist/types/services/NodeMQTTOrchestrator.d.ts +2 -2
  98. package/dist/types/services/interfaces/MQTTTransport.d.ts +10 -0
  99. package/dist/types/types/discovery.d.ts +1 -1
  100. package/dist/types/types/localControl.d.ts +26 -1
  101. package/dist/types/types/provision.d.ts +10 -0
  102. package/dist/types/types/transport.d.ts +24 -2
  103. package/dist/types/utils/baseUtils.d.ts +1 -1
  104. package/dist/types/utils/constants.d.ts +74 -5
  105. package/dist/types/utils/eventSubscriptionUtils.d.ts +19 -3
  106. package/dist/types/utils/waitForNodeOnline.d.ts +9 -3
  107. package/package.json +1 -1
  108. package/dist/cjs/proto/constants.js +0 -24
  109. package/dist/cjs/proto/constants.js.map +0 -1
  110. package/dist/cjs/proto/esp_local_ctrl.js +0 -787
  111. package/dist/cjs/proto/esp_local_ctrl.js.map +0 -1
  112. package/dist/esm/proto/constants.js +0 -24
  113. package/dist/esm/proto/constants.js.map +0 -1
  114. package/dist/esm/proto/esp_local_ctrl.js +0 -758
  115. package/dist/esm/proto/esp_local_ctrl.js.map +0 -1
  116. package/dist/types/proto/constants.d.ts +0 -15
  117. package/dist/types/proto/esp_local_ctrl.d.ts +0 -283
@@ -1,154 +1,221 @@
1
- import { ESPRMNeoBase } from '../../ESPRMNeoBase.js';
2
- import { Status } from '../../proto/constants.js';
3
- import { LocalCtrlMessage, LocalCtrlMsgType, CmdSetPropertyValues, PropertyValue, CmdGetPropertyCount, CmdGetPropertyValues } from '../../proto/esp_local_ctrl.js';
4
- import { Endpoint } from '../../utils/constants.js';
1
+ import { RMakerLocalCtrlDataType, RMakerLocalCtrlProtoHelper } from '../../proto/rmaker_local_ctrl.js';
2
+ import { ESPLocalControlProtocol } from '../../types/transport.js';
3
+ import { RMakerLocalCtrlEndpoint, RMakerLocalCtrlSetParamsStatus, RMAKER_LOCAL_CTRL_VERSION_KEY } from '../../utils/constants.js';
5
4
  import { uint8ArrayToBase64, base64ToUint8Array } from '../ESPRMNeoHelpers/TransformEncoding.js';
5
+ import { getLocalControlAdapter, ensureLocalControlSession } from './LocalControlSession.js';
6
6
 
7
7
  /*
8
8
  * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
9
9
  *
10
10
  * SPDX-License-Identifier: Apache-2.0
11
11
  */
12
- /** Property index of the writable "params" property exposed by RMNeo local-control firmware. */
13
- const LOCAL_CONTROL_PARAMS_INDEX = 1;
14
12
  /**
15
- * Built-in `local` transport. Communicates with the node over the LAN through
16
- * the app-supplied {@link ESPRMNeoBase.ESPLocalControlAdapter} using the
17
- * `esp_local_ctrl` protobuf protocol. Connection metadata (`baseUrl`,
18
- * `securityType`, `pop`, and `username` for sec2) is supplied via the transport
19
- * config by {@link delegatedTransportHandler}.
13
+ * Tail of the in-flight fragmented-read chain, per node id.
14
+ *
15
+ * Module-level on purpose: {@link delegatedTransportHandler} builds a fresh
16
+ * transport for every call, so a queue held on the instance would serialize
17
+ * nothing. Keyed by node, because the transfer cache is per device.
18
+ */
19
+ const nodeReadQueues = new Map();
20
+ /**
21
+ * Runs `read` after every fragmented read already queued for `nodeId`.
22
+ *
23
+ * A failed read must not wedge the queue, so the chain advances on rejection
24
+ * too, while the caller still sees the original rejection. The map entry is
25
+ * dropped once the chain drains, so a long-lived app doesn't retain one promise
26
+ * per node it has ever talked to.
27
+ *
28
+ * @param nodeId - Node whose reads are being serialized.
29
+ * @param read - Pull loop to run once the node is free.
30
+ */
31
+ function enqueueNodeRead(nodeId, read) {
32
+ const previous = nodeReadQueues.get(nodeId) ?? Promise.resolve();
33
+ const result = previous.then(read, read);
34
+ const tail = result.then(() => undefined, () => undefined);
35
+ nodeReadQueues.set(nodeId, tail);
36
+ void tail.then(() => {
37
+ // Only the current tail clears the entry; otherwise a newer read owns it.
38
+ if (nodeReadQueues.get(nodeId) === tail) {
39
+ nodeReadQueues.delete(nodeId);
40
+ }
41
+ });
42
+ return result;
43
+ }
44
+ /** Session endpoints of the `rmaker_local_ctrl` protocol, passed to the adapter. */
45
+ const RMAKER_SESSION_OPTIONS = {
46
+ protocol: ESPLocalControlProtocol.rmakerLocalCtrl,
47
+ sessionPath: RMakerLocalCtrlEndpoint.SESSION,
48
+ versionPath: RMakerLocalCtrlEndpoint.VERSION,
49
+ versionKey: RMAKER_LOCAL_CTRL_VERSION_KEY,
50
+ };
51
+ /**
52
+ * The built-in `local` transport, speaking the `rmaker_local_ctrl` endpoint
53
+ * protocol over the app-supplied {@link ESPRMNeoBase.ESPLocalControlAdapter}:
54
+ *
55
+ * - `set_params` carries the same raw JSON body as a cloud set-params call and
56
+ * answers `{"status":"success"}` / `{"status":"fail","description":…}`.
57
+ * - `get_params` / `get_config` exchange protobuf `CmdGetData`/`RespGetData` and
58
+ * are fragmented — the client pulls fixed-size chunks by offset until
59
+ * `TotalLen` is covered.
60
+ *
61
+ * Connection metadata (`baseUrl`, `securityType`, `pop`, and `username` for
62
+ * sec2) is supplied via the transport config by {@link
63
+ * delegatedTransportHandler}; security 0 is not offered by this protocol.
20
64
  */
21
65
  class ESPLocalControlTransport {
22
- payload;
23
66
  metadata;
24
- propertyInfo = {};
25
67
  constructor(transportConfig) {
26
68
  this.metadata = transportConfig.metadata ?? {};
27
69
  }
28
70
  get adapter() {
29
- const adapter = ESPRMNeoBase.getLocalControlAdapter();
30
- if (!adapter) {
31
- throw new Error("Local control adapter is not configured");
32
- }
33
- return adapter;
34
- }
35
- /**
36
- * Connects to the node, retrying on failure. RMNeo's adapter resolves on a
37
- * successful connection and rejects otherwise, so a resolved call is treated
38
- * as connected.
39
- */
40
- async connectWithRetry(nodeId, baseUrl, securityType, pop, username, maxRetries = 3) {
41
- let attempt = 0;
42
- let lastError;
43
- while (attempt < maxRetries) {
44
- try {
45
- await this.adapter.connect(nodeId, baseUrl, securityType, pop, username);
46
- return;
47
- }
48
- catch (error) {
49
- lastError = error;
50
- attempt += 1;
51
- }
52
- }
53
- const message = lastError instanceof Error ? lastError.message : String(lastError);
54
- throw new Error(`Failed to connect after ${maxRetries} attempts: ${message}`);
71
+ return getLocalControlAdapter();
55
72
  }
56
73
  async ensureConnected(nodeId) {
57
- const isConnected = await this.adapter.isConnected(nodeId);
58
- if (!isConnected) {
59
- await this.connectWithRetry(nodeId, this.metadata.baseUrl, this.metadata.securityType ?? 0, this.metadata.pop, this.metadata.username);
60
- }
74
+ await ensureLocalControlSession(this.adapter, nodeId, this.metadata, RMAKER_SESSION_OPTIONS);
61
75
  }
76
+ /**
77
+ * Applies params over `set_params`.
78
+ *
79
+ * @param payload - `{ node_id, payload }`, where `payload` is the
80
+ * `{ <deviceOrServiceName>: { <paramName>: value } }` map to write.
81
+ * @throws {Error} When the device reports a non-success status.
82
+ */
62
83
  async setParam(payload, _nodeRef) {
63
- this.payload = payload;
64
- await this.ensureConnected(this.payload?.node_id);
65
- const success = await this.setProperty(this.payload?.payload ?? {});
66
- if (!success) {
67
- throw new Error("Failed to set device params over local control");
68
- }
84
+ const nodeId = payload?.node_id;
85
+ await this.ensureConnected(nodeId);
86
+ const request = uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload?.payload ?? {})));
87
+ const response = await this.adapter.sendData(nodeId, RMakerLocalCtrlEndpoint.SET_PARAMS, request);
88
+ this.assertSetParamsAccepted(response);
69
89
  return { message: "Parameters updated successfully", statusCode: 200 };
70
90
  }
91
+ /**
92
+ * Reads the node's full params document over `get_params`.
93
+ *
94
+ * @param payload - `{ node_id }` identifying the node to read.
95
+ * @returns The params JSON, keyed by device/service name.
96
+ */
71
97
  async getParams(payload, _nodeRef) {
72
- this.payload = payload;
73
- await this.ensureConnected(this.payload?.node_id);
74
- return this.getPropertyInfo();
98
+ const nodeId = payload?.node_id;
99
+ await this.ensureConnected(nodeId);
100
+ return this.readJsonDocument(nodeId, RMakerLocalCtrlDataType.TypeParams, RMakerLocalCtrlEndpoint.GET_PARAMS);
75
101
  }
76
- // ── set ──────────────────────────────────────────────────────────────────
77
- async setProperty(json) {
78
- const request = this.buildSetPropertyRequest(json);
79
- const response = await this.adapter.sendData(this.payload?.node_id, Endpoint.LOCAL_CTRL, request);
80
- return this.processSetPropertyResponse(response);
81
- }
82
- buildSetPropertyRequest(json) {
83
- const message = new LocalCtrlMessage();
84
- message.msg = LocalCtrlMsgType.TypeCmdSetPropertyValues;
85
- const cmd = new CmdSetPropertyValues();
86
- const prop = new PropertyValue();
87
- prop.index = LOCAL_CONTROL_PARAMS_INDEX;
88
- prop.value = new TextEncoder().encode(JSON.stringify(json ?? {}));
89
- cmd.props.push(prop);
90
- message.cmd_set_prop_vals = cmd;
91
- return uint8ArrayToBase64(message.serialize());
92
- }
93
- processSetPropertyResponse(response) {
94
- const deserialized = LocalCtrlMessage.deserialize(base64ToUint8Array(response));
95
- return deserialized.resp_set_prop_vals.status === Status.Success;
102
+ /**
103
+ * Reads the node's config document over `get_config`. Not part of
104
+ * {@link ESPTransportInterface} — the node config normally comes from the
105
+ * cloud; this serves LAN-only flows.
106
+ *
107
+ * @param nodeId - Node to read from.
108
+ * @returns The node config JSON.
109
+ */
110
+ async getConfig(nodeId) {
111
+ await this.ensureConnected(nodeId);
112
+ return this.readJsonDocument(nodeId, RMakerLocalCtrlDataType.TypeConfig, RMakerLocalCtrlEndpoint.GET_CONFIG);
96
113
  }
97
- // ── get ──────────────────────────────────────────────────────────────────
98
- /** Fetches the property count, then reads each property value into propertyInfo. */
99
- async getPropertyInfo() {
100
- this.propertyInfo = {};
101
- const count = await this.fetchPropertyCount();
102
- for (let index = 0; index < count; index++) {
103
- await this.fetchPropertyValue(index);
114
+ // ── set ──────────────────────────────────────────────────────────────────
115
+ /**
116
+ * Validates a `set_params` raw-JSON response.
117
+ *
118
+ * @throws {Error} When the body is unparseable or reports a failure.
119
+ */
120
+ assertSetParamsAccepted(response) {
121
+ const text = new TextDecoder().decode(base64ToUint8Array(response ?? ""));
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(text);
104
125
  }
105
- return this.propertyInfo;
106
- }
107
- async fetchPropertyCount() {
108
- const response = await this.adapter.sendData(this.payload?.node_id, Endpoint.LOCAL_CTRL, this.buildGetPropertyCountRequest());
109
- return this.processGetPropertyCountResponse(response);
110
- }
111
- buildGetPropertyCountRequest() {
112
- const request = new LocalCtrlMessage();
113
- request.msg = LocalCtrlMsgType.TypeCmdGetPropertyCount;
114
- request.cmd_get_prop_count = new CmdGetPropertyCount();
115
- return uint8ArrayToBase64(request.serialize());
116
- }
117
- processGetPropertyCountResponse(response) {
118
- const deserialized = LocalCtrlMessage.deserialize(base64ToUint8Array(response));
119
- if (deserialized.resp_get_prop_count.status !== Status.Success) {
120
- throw new Error("Failed to retrieve property count from device");
126
+ catch {
127
+ throw new Error(`Unexpected set_params response from device: ${text || "<empty>"}`);
128
+ }
129
+ if (parsed?.status !== RMakerLocalCtrlSetParamsStatus.SUCCESS) {
130
+ throw new Error(parsed?.description ??
131
+ "Failed to set device params over local control (rmaker_local_ctrl)");
121
132
  }
122
- return deserialized.resp_get_prop_count.count;
123
133
  }
124
- async fetchPropertyValue(index) {
125
- const response = await this.adapter.sendData(this.payload?.node_id, Endpoint.LOCAL_CTRL, this.buildGetPropertyValueRequest(index));
126
- const property = this.processGetPropertyValueResponse(response);
127
- if (property) {
128
- this.propertyInfo[property.name] = property.value;
134
+ // ── get ──────────────────────────────────────────────────────────────────
135
+ /**
136
+ * Pulls a fragmented document and parses it as JSON, serialized against every
137
+ * other fragmented read of the same node.
138
+ *
139
+ * The device holds **one global transfer cache**, not one per session: an
140
+ * offset-0 request regenerates it and it is freed after the last fragment. So
141
+ * a `getParams()` racing a `getConfig()` on the same node would clobber the
142
+ * other — the second offset-0 regenerates the cache mid-transfer, and the
143
+ * first read's next fragment comes back `Fail` (or, worse, carries bytes from
144
+ * the wrong document). The queue makes that interleaving impossible rather
145
+ * than detecting it after the fact.
146
+ *
147
+ * @param nodeId - Node to read from.
148
+ * @param dataType - Which document to read (params or config).
149
+ * @param endpoint - Endpoint serving that document.
150
+ * @throws {Error} When the device reports a failure, the response is
151
+ * malformed, or a fragment makes no forward progress.
152
+ */
153
+ readJsonDocument(nodeId, dataType, endpoint) {
154
+ return enqueueNodeRead(nodeId, () => this.pullJsonDocument(nodeId, dataType, endpoint));
155
+ }
156
+ /** The client-pull loop itself; always reached via {@link readJsonDocument}. */
157
+ async pullJsonDocument(nodeId, dataType, endpoint) {
158
+ const fragments = [];
159
+ let offset = 0;
160
+ let totalLength = 0;
161
+ do {
162
+ const response = await this.adapter.sendData(nodeId, endpoint, this.buildGetDataRequest(dataType, offset));
163
+ const buf = this.processGetDataResponse(response, offset);
164
+ totalLength = buf.totalLength;
165
+ if (totalLength === 0) {
166
+ return {};
167
+ }
168
+ if (buf.payload.length === 0) {
169
+ // Without forward progress the pull loop would never terminate.
170
+ throw new Error(`Device returned an empty fragment at offset ${offset} of ${totalLength} on ${endpoint}`);
171
+ }
172
+ fragments.push(buf.payload);
173
+ offset += buf.payload.length;
174
+ } while (offset < totalLength);
175
+ if (offset > totalLength) {
176
+ throw new Error(`Fragmented read of ${endpoint} overran: got ${offset} bytes, expected ${totalLength}`);
129
177
  }
178
+ return this.parseJsonFragments(fragments, endpoint);
130
179
  }
131
- buildGetPropertyValueRequest(index) {
132
- const request = new LocalCtrlMessage();
133
- request.msg = LocalCtrlMsgType.TypeCmdGetPropertyValues;
134
- const cmd = new CmdGetPropertyValues();
135
- cmd.indices.push(index);
136
- request.cmd_get_prop_vals = cmd;
137
- return uint8ArrayToBase64(request.serialize());
180
+ buildGetDataRequest(dataType, offset) {
181
+ return uint8ArrayToBase64(RMakerLocalCtrlProtoHelper.createGetDataRequest(dataType, offset));
138
182
  }
139
- processGetPropertyValueResponse(response) {
140
- const deserialized = LocalCtrlMessage.deserialize(base64ToUint8Array(response));
141
- if (deserialized.resp_get_prop_vals.status !== Status.Success) {
142
- throw new Error("Failed to get property values from device response");
183
+ /**
184
+ * Parses one `RespGetData` and checks it answers the requested offset.
185
+ *
186
+ * @param response - Base64 protobuf response from the adapter.
187
+ * @param requestedOffset - Offset asked for, used to detect a desynced pull.
188
+ */
189
+ processGetDataResponse(response, requestedOffset) {
190
+ const parsed = RMakerLocalCtrlProtoHelper.parseGetDataResponse(base64ToUint8Array(response));
191
+ if (!RMakerLocalCtrlProtoHelper.isSuccess(parsed)) {
192
+ throw new Error(`Device rejected the local-control read (status ${RMakerLocalCtrlProtoHelper.getStatus(parsed)})`);
193
+ }
194
+ const fragmentOffset = RMakerLocalCtrlProtoHelper.getOffset(parsed);
195
+ if (fragmentOffset !== requestedOffset) {
196
+ throw new Error(`Device answered offset ${fragmentOffset}, expected ${requestedOffset}`);
143
197
  }
144
- const prop = deserialized.resp_get_prop_vals.props[0];
145
- if (!prop)
146
- return undefined;
147
198
  return {
148
- name: prop.name || "unknown",
149
- value: JSON.parse(new TextDecoder().decode(prop.value)),
199
+ payload: RMakerLocalCtrlProtoHelper.getPayload(parsed),
200
+ totalLength: RMakerLocalCtrlProtoHelper.getTotalLen(parsed),
150
201
  };
151
202
  }
203
+ /** Joins the pulled fragments and parses the result as JSON. */
204
+ parseJsonFragments(fragments, endpoint) {
205
+ const joined = new Uint8Array(fragments.reduce((length, fragment) => length + fragment.length, 0));
206
+ let position = 0;
207
+ for (const fragment of fragments) {
208
+ joined.set(fragment, position);
209
+ position += fragment.length;
210
+ }
211
+ const text = new TextDecoder().decode(joined);
212
+ try {
213
+ return JSON.parse(text);
214
+ }
215
+ catch {
216
+ throw new Error(`Device returned malformed JSON on ${endpoint}`);
217
+ }
218
+ }
152
219
  }
153
220
 
154
221
  export { ESPLocalControlTransport };
@@ -1 +1 @@
1
- {"version":3,"file":"ESPLocalControlTransport.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ESPLocalControlTransport.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,59 @@
1
+ import { ESPRMNeoBase } from '../../ESPRMNeoBase.js';
2
+
3
+ /*
4
+ * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
5
+ *
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+ /** Attempts made before a local-control handshake is reported as failed. */
9
+ const DEFAULT_CONNECT_RETRIES = 3;
10
+ /**
11
+ * Returns the app-supplied local control adapter.
12
+ *
13
+ * @throws {Error} When no adapter was registered on {@link ESPRMNeoBase}.
14
+ */
15
+ function getLocalControlAdapter() {
16
+ const adapter = ESPRMNeoBase.getLocalControlAdapter();
17
+ if (!adapter) {
18
+ throw new Error("Local control adapter is not configured");
19
+ }
20
+ return adapter;
21
+ }
22
+ /**
23
+ * Ensures a usable local-control session for `nodeId`, connecting (with
24
+ * retries) when the adapter reports none.
25
+ *
26
+ * The adapter resolves on a successful handshake and rejects otherwise, so a
27
+ * resolved `connect` is treated as connected.
28
+ *
29
+ * @param adapter - Adapter to drive.
30
+ * @param nodeId - Node to connect to.
31
+ * @param metadata - Local transport metadata (`baseUrl`, `securityType`, `pop`,
32
+ * `username`).
33
+ * @param options - Session endpoints for the protocol in use; omitted for the
34
+ * adapter's built-in default paths.
35
+ * @param maxRetries - Connection attempts before failing.
36
+ * @throws {Error} When every connection attempt fails.
37
+ */
38
+ async function ensureLocalControlSession(adapter, nodeId, metadata, options, maxRetries = DEFAULT_CONNECT_RETRIES) {
39
+ if (await adapter.isConnected(nodeId)) {
40
+ return;
41
+ }
42
+ let attempt = 0;
43
+ let lastError;
44
+ while (attempt < maxRetries) {
45
+ try {
46
+ await adapter.connect(nodeId, metadata.baseUrl, metadata.securityType ?? 0, metadata.pop, metadata.username, options);
47
+ return;
48
+ }
49
+ catch (error) {
50
+ lastError = error;
51
+ attempt += 1;
52
+ }
53
+ }
54
+ const message = lastError instanceof Error ? lastError.message : String(lastError);
55
+ throw new Error(`Failed to connect after ${maxRetries} attempts: ${message}`);
56
+ }
57
+
58
+ export { ensureLocalControlSession, getLocalControlAdapter };
59
+ //# sourceMappingURL=LocalControlSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalControlSession.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -17,6 +17,29 @@ var ESPTransportMode;
17
17
  /** Communication is routed through MQTT (RainMaker Neo cloud: AWS shadow). */
18
18
  ESPTransportMode["mqtt"] = "mqtt";
19
19
  })(ESPTransportMode || (ESPTransportMode = {}));
20
+ /**
21
+ * Wire protocol spoken by the `local` transport. Both protocols run over a
22
+ * protocomm session on the node's LAN HTTP server; they differ in endpoint
23
+ * names and message encoding.
24
+ *
25
+ * Carried on the local transport config's `metadata.protocol`, set by local
26
+ * discovery from the mDNS service type that produced the hit.
27
+ */
28
+ var ESPLocalControlProtocol;
29
+ (function (ESPLocalControlProtocol) {
30
+ /**
31
+ * RainMaker Neo protocol (`rmaker_local_ctrl/*` session plus
32
+ * `get_params`/`set_params`/`get_config`), advertised as
33
+ * `_esp_rmaker_ctrl._tcp`. Default for this SDK.
34
+ */
35
+ ESPLocalControlProtocol["rmakerLocalCtrl"] = "rmaker_local_ctrl";
36
+ })(ESPLocalControlProtocol || (ESPLocalControlProtocol = {}));
37
+ /**
38
+ * Protocol assumed when a local transport config carries no explicit
39
+ * `metadata.protocol` — for example a LAN transport restored from a client-side
40
+ * registry rather than a fresh discovery hit.
41
+ */
42
+ const DEFAULT_LOCAL_CONTROL_PROTOCOL = ESPLocalControlProtocol.rmakerLocalCtrl;
20
43
  /**
21
44
  * Default transport priority order used when neither the node nor the SDK base
22
45
  * has an explicit order configured: local control first, MQTT (cloud) fallback.
@@ -26,5 +49,5 @@ const DEFAULT_TRANSPORT_ORDER = [
26
49
  ESPTransportMode.mqtt,
27
50
  ];
28
51
 
29
- export { DEFAULT_TRANSPORT_ORDER, ESPTransportMode };
52
+ export { DEFAULT_LOCAL_CONTROL_PROTOCOL, DEFAULT_TRANSPORT_ORDER, ESPLocalControlProtocol, ESPTransportMode };
30
53
  //# sourceMappingURL=transport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"transport.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
  */
@@ -464,8 +473,6 @@ const ProvisionType = {
464
473
  * An object containing endpoint paths.
465
474
  */
466
475
  const Endpoint = {
467
- /** The endpoint for local control. */
468
- LOCAL_CTRL: "esp_local_ctrl/control",
469
476
  /** The endpoint for cloud user association. */
470
477
  CLOUD_USER_ASSOCIATION: "cloud_user_assoc",
471
478
  /** The endpoint for challenge-response and get-node-id protocol with the device. */
@@ -473,6 +480,64 @@ const Endpoint = {
473
480
  /** The endpoint for assisted claiming. */
474
481
  RM_CLAIM: "rmaker_claim",
475
482
  };
483
+ /**
484
+ * Endpoint paths of the `rmaker_local_ctrl` protocol served by RainMaker Neo
485
+ * firmware (see the firmware's local-control endpoint protocol spec).
486
+ *
487
+ * All data endpoints inherit the security of the session established on
488
+ * {@link RMakerLocalCtrlEndpoint.SESSION}.
489
+ */
490
+ const RMakerLocalCtrlEndpoint = {
491
+ /** Protocomm session-security endpoint (SEC1 with/without PoP, or SEC2). */
492
+ SESSION: "rmaker_local_ctrl/session",
493
+ /** POST any payload; responds with the service info JSON (`sec_ver`, `cap`, …). */
494
+ VERSION: "rmaker_local_ctrl/version",
495
+ /** Reads the params JSON — protobuf `CmdGetData`, fragmented response. */
496
+ GET_PARAMS: "get_params",
497
+ /** Reads the node config JSON — protobuf `CmdGetData`, fragmented response. */
498
+ GET_CONFIG: "get_config",
499
+ /** Writes params — raw JSON request and response (no protobuf). */
500
+ SET_PARAMS: "set_params",
501
+ };
502
+ /**
503
+ * Root key of the JSON served by {@link RMakerLocalCtrlEndpoint.VERSION}, i.e.
504
+ * `{"rmaker_local_ctrl": {"ver": …, "sec_ver": …, "cap": [...]}}`. Passed to the
505
+ * local-control adapter so the native layer probes the right scheme version.
506
+ */
507
+ const RMAKER_LOCAL_CTRL_VERSION_KEY = "rmaker_local_ctrl";
508
+ /**
509
+ * Maximum fragment size (bytes) the firmware serves per `RespGetData`. Reads
510
+ * loop with `Offset += Payload.length` until `TotalLen` is reached; this value
511
+ * is informational (the device dictates the actual fragment length).
512
+ */
513
+ const RMAKER_LOCAL_CTRL_FRAGMENT_SIZE = 200;
514
+ /**
515
+ * `status` values in a `set_params` raw-JSON response.
516
+ */
517
+ const RMakerLocalCtrlSetParamsStatus = {
518
+ SUCCESS: "success",
519
+ FAIL: "fail",
520
+ };
521
+ /**
522
+ * mDNS TXT record keys advertised by the `_esp_rmaker_ctrl._tcp` service.
523
+ */
524
+ const RMakerLocalCtrlTxtKey = {
525
+ /** Node ID (also the service instance name and hostname). */
526
+ NODE_ID: "node_id",
527
+ /** Comma-separated active capabilities — see {@link RMakerLocalCtrlCapability}. */
528
+ CAP: "cap",
529
+ };
530
+ /**
531
+ * Capability tokens found in the `cap` TXT record. A node advertising only
532
+ * `ch_resp` is reachable for on-network user-node association but *not* for
533
+ * param control, so it must not be registered as a local control transport.
534
+ */
535
+ const RMakerLocalCtrlCapability = {
536
+ /** Params/config endpoints are registered. */
537
+ LOCAL_CTRL: "local_ctrl",
538
+ /** Challenge-response (on-network user-node association) is registered. */
539
+ CH_RESP: "ch_resp",
540
+ };
476
541
  /**
477
542
  * Assisted-claiming REST paths, on the deployment's main API and SigV4-signed
478
543
  * like every other `/v1/*` route.
@@ -630,8 +695,12 @@ const ProvErrorCodes = {
630
695
  * @enum {string}
631
696
  */
632
697
  const ServiceType = {
633
- /** Represents the ESP local control TCP service type. */
634
- ESP_LOCAL_CTRL_TCP: "_esp_local_ctrl._tcp.",
698
+ /**
699
+ * Service type advertised by RainMaker Neo firmware. A single instance serves
700
+ * the `rmaker_local_ctrl` endpoints; the `cap` TXT record says which endpoint
701
+ * sets are active. This is the default for local discovery in this SDK.
702
+ */
703
+ ESP_RMAKER_LOCAL_CTRL_TCP: "_esp_rmaker_ctrl._tcp.",
635
704
  };
636
705
  /**
637
706
  * An object containing protocol types.
@@ -840,5 +909,5 @@ const SubscriptionChannelIds = {
840
909
  MQTT: "mqtt",
841
910
  };
842
911
 
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 };
912
+ 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, RMAKER_LOCAL_CTRL_FRAGMENT_SIZE, RMAKER_LOCAL_CTRL_VERSION_KEY, RMakerLocalCtrlCapability, RMakerLocalCtrlEndpoint, RMakerLocalCtrlSetParamsStatus, RMakerLocalCtrlTxtKey, SDK_VERSION, ScheduleErrorMessages, ScheduleSuccessMessages, ServiceType, SharingSuccessMessages, StatusMessage, StorageAdapterErrorCodes, StorageKeys, SubscriptionChannelIds, TokenErrorCodes, TriggerErrorMessages, TriggerSuccessMessages, ValidationErrorCodes };
844
913
  //# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}