node-red-contrib-uos-nats 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ npm install node-red-contrib-uos-nats
22
22
  npm install /path/to/local/NATS-NodeRED
23
23
  ```
24
24
 
25
- Restart Node-RED. You will find the nodes under the *u-OS Data Hub* category.
25
+ Restart Node-RED. You will find the nodes under the *DataHub-NATS* category.
26
26
 
27
27
  ## u-OS Config Node
28
28
 
@@ -31,15 +31,15 @@ Fields:
31
31
  - **Host / Port** – IP address of your controller (e.g. `192.168.10.100`) and the NATS port `49360`.
32
32
  - **Client Name** – used for the NATS inbox prefix (`_INBOX.<name>`).
33
33
  - **Client ID / Secret** – OAuth2 client credentials created in the Control Center.
34
- - **Token URL** – optional override, defaults to `https://<host>/oauth2/token`.
35
34
  - **Scope** – usually `hub.variables.provide hub.variables.readwrite`.
35
+ - **Granted scopes** – click *Refresh* to query the token endpoint and show the scopes currently granted to that client.
36
36
 
37
- The config node automatically fetches tokens via Client Credentials flow and exposes helper endpoints so other nodes can list providers and variables.
37
+ The config node automatically fetches tokens via Client Credentials flow and exposes helper endpoints so other nodes can list providers and variables. The token endpoint is derived from the configured host (`https://<host>/oauth2/token`), so there is no additional field to maintain.
38
38
 
39
39
  ## DataHub Input Node
40
40
 
41
- - Select the u-OS config node and enter the provider ID (e.g. `u_os_adm`).
42
- - Optionally provide a comma-separated list of variable keys (leave empty to receive all).
41
+ - Select the u-OS config node, then choose one of the discovered providers from the dropdown (the node queries `/datahub/v1/providers` for you).
42
+ - Pick the variables you need from the multi-select list. Leave it empty to receive all variables from the provider.
43
43
  - The node outputs messages with the structure:
44
44
  ```json
45
45
  {
@@ -5,23 +5,193 @@
5
5
  defaults: {
6
6
  name: { value: '' },
7
7
  connection: { type: 'uos-config', required: true },
8
- providerId: { value: 'sampleprovider', required: true },
8
+ providerId: { value: '', required: true },
9
9
  variablesText: { value: '' }
10
10
  },
11
11
  inputs: 0,
12
12
  outputs: 1,
13
13
  icon: "white/datahub-input.svg",
14
14
  oneditprepare: function() {
15
- const help = `
16
- <b>DataHub Input node</b><br>
17
- <ul>
18
- <li><b>u-OS Config</b>: choose the config node with host and OAuth credentials.</li>
19
- <li><b>Provider ID</b>: e.g. <code>sampleprovider</code> or <code>u_os_adm</code>.</li>
20
- <li><b>Variables</b>: leave empty for all variables or enter comma-separated keys like <code>diagnostics.status_text,diagnostics.error_count</code>.</li>
21
- </ul>
22
- The node outputs messages of the form <code>{ type: 'snapshot'|'change', variables: [...] }</code>.
23
- `;
24
- $('#datahub-input-help').html(help);
15
+ const node = this;
16
+ const $config = $('#node-input-connection');
17
+ const $provider = $('#node-input-providerId');
18
+ const $providerStatus = $('#datahub-provider-status');
19
+ const $variablesHidden = $('#node-input-variablesText');
20
+ const $variables = $('#datahub-input-variables');
21
+ const $variablesStatus = $('#datahub-variables-status');
22
+
23
+ const initialProvider = node.providerId;
24
+ const initialVars = ($variablesHidden.val() || '')
25
+ .split(',')
26
+ .map((entry) => entry.trim())
27
+ .filter((entry) => entry.length > 0);
28
+
29
+ const normalizeProviders = (payload) => {
30
+ if (!payload) {
31
+ return [];
32
+ }
33
+ if (Array.isArray(payload)) {
34
+ return payload;
35
+ }
36
+ if (Array.isArray(payload.providers)) {
37
+ return payload.providers;
38
+ }
39
+ return [];
40
+ };
41
+
42
+ const normalizeVariables = (payload) => {
43
+ if (!payload) {
44
+ return [];
45
+ }
46
+ if (Array.isArray(payload)) {
47
+ return payload;
48
+ }
49
+ if (Array.isArray(payload.variables)) {
50
+ return payload.variables;
51
+ }
52
+ return [];
53
+ };
54
+
55
+ const providerLabel = (provider) => provider.name || provider.displayName || provider.providerId || provider.id || 'Unnamed provider';
56
+ const providerValue = (provider) => provider.providerId || provider.id || provider.name || '';
57
+ const variableValue = (variable) => variable.key || variable.name || variable.path || variable.id || '';
58
+ const variableLabel = (variable) => variable.tagName || variable.key || variable.name || variable.path || variable.id || 'Unnamed variable';
59
+
60
+ const syncVariablesField = () => {
61
+ const values = $variables.val() || [];
62
+ $variablesHidden.val(values.join(','));
63
+ };
64
+
65
+ let providerRequest = 0;
66
+ let variableRequest = 0;
67
+
68
+ const setProviderStatus = (text) => $providerStatus.text(text || '');
69
+ const setVariableStatus = (text) => $variablesStatus.text(text || '');
70
+
71
+ const loadVariables = (providerId, keepSelection = false) => {
72
+ const configId = $config.val();
73
+ if (!configId || !providerId) {
74
+ $variables.empty();
75
+ setVariableStatus('Select a provider to load variables.');
76
+ return;
77
+ }
78
+ const seq = ++variableRequest;
79
+ setVariableStatus('Loading variables…');
80
+ $variables.prop('disabled', true);
81
+ $.getJSON(`uos/providers/${configId}/${encodeURIComponent(providerId)}/variables`)
82
+ .done((payload) => {
83
+ if (seq !== variableRequest) {
84
+ return;
85
+ }
86
+ const vars = normalizeVariables(payload);
87
+ $variables.empty();
88
+ if (!vars.length) {
89
+ setVariableStatus('No variables returned. Check Data Hub permissions.');
90
+ $variables.prop('disabled', true);
91
+ syncVariablesField();
92
+ return;
93
+ }
94
+ vars.forEach((variable) => {
95
+ const value = variableValue(variable);
96
+ if (!value) {
97
+ return;
98
+ }
99
+ const option = $('<option></option>')
100
+ .attr('value', value)
101
+ .text(variableLabel(variable));
102
+ $variables.append(option);
103
+ });
104
+ $variables.prop('disabled', false);
105
+ const preset = keepSelection ? ($variablesHidden.val() || '').split(',').map((entry) => entry.trim()).filter(Boolean) : initialVars;
106
+ if (preset.length) {
107
+ $variables.val(preset);
108
+ }
109
+ syncVariablesField();
110
+ setVariableStatus('Select one or multiple variables (Ctrl/Cmd + click). Leave empty to receive all.');
111
+ })
112
+ .fail((xhr) => {
113
+ const msg = xhr?.responseJSON?.error || xhr.statusText || 'Failed to load variables.';
114
+ setVariableStatus(msg);
115
+ $variables.prop('disabled', true);
116
+ $variables.empty();
117
+ syncVariablesField();
118
+ });
119
+ };
120
+
121
+ const loadProviders = () => {
122
+ const configId = $config.val();
123
+ if (!configId) {
124
+ $provider.empty();
125
+ $provider.append('<option value="">Select a u-OS config first</option>');
126
+ $provider.prop('disabled', true);
127
+ setProviderStatus('Pick or create a u-OS config node before selecting a provider.');
128
+ setVariableStatus('');
129
+ $variables.empty();
130
+ $variables.prop('disabled', true);
131
+ return;
132
+ }
133
+ const seq = ++providerRequest;
134
+ setProviderStatus('Loading providers…');
135
+ $provider.prop('disabled', true);
136
+ $.getJSON(`uos/providers/${configId}`)
137
+ .done((payload) => {
138
+ if (seq !== providerRequest) {
139
+ return;
140
+ }
141
+ const list = normalizeProviders(payload);
142
+ $provider.empty();
143
+ if (!list.length) {
144
+ $provider.append('<option value="">No providers available</option>');
145
+ setProviderStatus('No providers returned. Verify Data Hub access.');
146
+ $provider.prop('disabled', true);
147
+ return;
148
+ }
149
+ list.forEach((prov) => {
150
+ const value = providerValue(prov);
151
+ if (!value) {
152
+ return;
153
+ }
154
+ const option = $('<option></option>')
155
+ .attr('value', value)
156
+ .text(providerLabel(prov));
157
+ $provider.append(option);
158
+ });
159
+ $provider.prop('disabled', false);
160
+ if (initialProvider) {
161
+ $provider.val(initialProvider);
162
+ }
163
+ if (!$provider.val()) {
164
+ $provider.val($provider.find('option:first').attr('value'));
165
+ }
166
+ setProviderStatus('Choose the provider you want to subscribe to.');
167
+ loadVariables($provider.val());
168
+ })
169
+ .fail((xhr) => {
170
+ const msg = xhr?.responseJSON?.error || xhr.statusText || 'Failed to load providers.';
171
+ $provider.empty();
172
+ $provider.append('<option value="">Unable to load providers</option>');
173
+ $provider.prop('disabled', true);
174
+ setProviderStatus(msg);
175
+ });
176
+ };
177
+
178
+ $variables.on('change', () => {
179
+ syncVariablesField();
180
+ });
181
+
182
+ $provider.on('change', () => {
183
+ $variablesHidden.val('');
184
+ syncVariablesField();
185
+ loadVariables($provider.val(), false);
186
+ });
187
+
188
+ $config.on('change', () => {
189
+ loadProviders();
190
+ });
191
+
192
+ syncVariablesField();
193
+ // Delay provider loading slightly to ensure the config typedInput is ready.
194
+ setTimeout(() => loadProviders(), 100);
25
195
  },
26
196
  label: function () {
27
197
  return this.name || `Input ${this.providerId || ''}`;
@@ -32,20 +202,51 @@
32
202
  });
33
203
  </script>
34
204
 
35
- <div class="form-row">
36
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
37
- <input type="text" id="node-input-name">
38
- </div>
39
- <div class="form-row">
40
- <label for="node-input-connection"><i class="fa fa-cube"></i> u-OS Config</label>
41
- <input type="text" id="node-input-connection">
42
- </div>
43
- <div class="form-row">
44
- <label for="node-input-providerId"><i class="fa fa-id-badge"></i> Provider ID</label>
45
- <input type="text" id="node-input-providerId" placeholder="sampleprovider">
46
- </div>
47
- <div class="form-row">
48
- <label for="node-input-variablesText"><i class="fa fa-list"></i> Variables (comma separated)</label>
49
- <input type="text" id="node-input-variablesText" placeholder="diagnostics.status_text,diagnostics.error_count">
50
- </div>
51
- <div id="datahub-input-help" class="form-tips"></div>
205
+ <script type="text/html" data-template-name="datahub-input">
206
+ <div class="form-row">
207
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
208
+ <input type="text" id="node-input-name">
209
+ </div>
210
+ <div class="form-row">
211
+ <label for="node-input-connection"><i class="fa fa-cube"></i> u-OS Config</label>
212
+ <input type="text" id="node-input-connection">
213
+ </div>
214
+ <div class="form-row">
215
+ <label for="node-input-providerId"><i class="fa fa-id-badge"></i> Provider</label>
216
+ <select id="node-input-providerId"></select>
217
+ </div>
218
+ <div class="form-row">
219
+ <label></label>
220
+ <div id="datahub-provider-status" class="form-tips"></div>
221
+ </div>
222
+ <div class="form-row">
223
+ <label for="datahub-input-variables"><i class="fa fa-list"></i> Variables</label>
224
+ <div style="flex:1;">
225
+ <select id="datahub-input-variables" multiple size="6" style="width:100%;"></select>
226
+ <input type="hidden" id="node-input-variablesText">
227
+ </div>
228
+ </div>
229
+ <div class="form-row">
230
+ <label></label>
231
+ <div id="datahub-variables-status" class="form-tips">Leave empty to listen to every variable.</div>
232
+ </div>
233
+ </script>
234
+
235
+ <script type="text/html" data-help-name="datahub-input">
236
+ <p><strong>DataHub Input</strong> subscribes to a provider via the u-OS Data Hub and emits JSON messages.</p>
237
+ <dl>
238
+ <dt>u-OS Config</dt>
239
+ <dd>Pick the connection node that holds host, port and OAuth credentials.</dd>
240
+ <dt>Provider ID</dt>
241
+ <dd>Provider to read. The drop-down lists every provider returned by the Control Center REST API.</dd>
242
+ <dt>Variables</dt>
243
+ <dd>Select one or more variable keys (single/multi-select). Leave unselected to stream all variables from the provider.</dd>
244
+ </dl>
245
+ <p>Each message has <code>msg.payload</code> shaped like:</p>
246
+ <pre>{
247
+ "type": "snapshot" | "change",
248
+ "variables": [
249
+ { "name": "diagnostics.status_text", "value": "ready" }
250
+ ]
251
+ }</pre>
252
+ </script>
@@ -10,17 +10,7 @@
10
10
  inputs: 1,
11
11
  outputs: 1,
12
12
  icon: "white/datahub-output.svg",
13
- oneditprepare: function() {
14
- const help = `
15
- <b>DataHub Output node</b><br>
16
- <ul>
17
- <li><b>u-OS Config</b>: select the config node.</li>
18
- <li><b>Provider ID</b>: defaults to <code>nodered</code> (will be registered automatically).</li>
19
- </ul>
20
- Send any JSON payload → nested objects become dot-separated keys (e.g. <code>{ diagnostics: { status_text: 'ok' } }</code> ⇢ <code>diagnostics.status_text</code>).
21
- `;
22
- $('#datahub-output-help').html(help);
23
- },
13
+ oneditprepare: function() {},
24
14
  label: function () {
25
15
  return this.name || `Output ${this.providerId || ''}`;
26
16
  },
@@ -30,16 +20,35 @@
30
20
  });
31
21
  </script>
32
22
 
33
- <div class="form-row">
34
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
35
- <input type="text" id="node-input-name">
36
- </div>
37
- <div class="form-row">
38
- <label for="node-input-connection"><i class="fa fa-cube"></i> u-OS Config</label>
39
- <input type="text" id="node-input-connection">
40
- </div>
41
- <div class="form-row">
42
- <label for="node-input-providerId"><i class="fa fa-id-badge"></i> Provider ID</label>
43
- <input type="text" id="node-input-providerId" placeholder="nodered">
44
- </div>
45
- <div id="datahub-output-help" class="form-tips"></div>
23
+ <script type="text/html" data-template-name="datahub-output">
24
+ <div class="form-row">
25
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
26
+ <input type="text" id="node-input-name">
27
+ </div>
28
+ <div class="form-row">
29
+ <label for="node-input-connection"><i class="fa fa-cube"></i> u-OS Config</label>
30
+ <input type="text" id="node-input-connection">
31
+ </div>
32
+ <div class="form-row">
33
+ <label for="node-input-providerId"><i class="fa fa-id-badge"></i> Provider ID</label>
34
+ <input type="text" id="node-input-providerId" placeholder="nodered">
35
+ </div>
36
+ </script>
37
+
38
+ <script type="text/html" data-help-name="datahub-output">
39
+ <p><strong>DataHub Output</strong> registers (if needed) and writes variables for the provider you specify.</p>
40
+ <dl>
41
+ <dt>u-OS Config</dt>
42
+ <dd>Connection node with host/port and OAuth credentials.</dd>
43
+ <dt>Provider ID</dt>
44
+ <dd>Defaults to <code>nodered</code>; change it if you want to publish under another provider name.</dd>
45
+ </dl>
46
+ <p>Send a JSON object as <code>msg.payload</code>. Nested objects are flattened using dot-notation:</p>
47
+ <pre>{
48
+ "diagnostics": {
49
+ "status_text": "running",
50
+ "error_count": 0
51
+ }
52
+ }</pre>
53
+ <p>becomes variables <code>diagnostics.status_text</code> and <code>diagnostics.error_count</code> in the Data Hub.</p>
54
+ </script>
@@ -2,10 +2,9 @@
2
2
  RED.nodes.registerType('uos-config', {
3
3
  category: 'config',
4
4
  defaults: {
5
- host: { value: '192.168.10.100', required: true },
5
+ host: { value: '127.0.0.1', required: true },
6
6
  port: { value: 49360, required: true, validate: RED.validators.number() },
7
7
  clientName: { value: 'nodered', required: true },
8
- tokenEndpoint: { value: '', required: false },
9
8
  scope: { value: 'hub.variables.provide hub.variables.readwrite', required: true },
10
9
  },
11
10
  credentials: {
@@ -15,34 +14,95 @@
15
14
  label: function () {
16
15
  return this.clientName ? `u-OS ${this.clientName}` : 'u-OS NATS';
17
16
  },
17
+ oneditprepare: function() {
18
+ const node = this;
19
+ const $scopeDisplay = $('#uos-scope-display');
20
+ const $scopeSpinner = $('#uos-scope-spinner');
21
+ const $refresh = $('#uos-scope-refresh');
22
+ const updateDisplay = (text, cls = '') => {
23
+ $scopeDisplay.text(text);
24
+ $scopeDisplay.removeClass('form-tips-warning form-tips-error');
25
+ if (cls) {
26
+ $scopeDisplay.addClass(cls);
27
+ }
28
+ };
29
+ const fetchScopes = () => {
30
+ const configId = node.id;
31
+ if (!configId || !node.credentials || !node.credentials.clientId) {
32
+ updateDisplay('Save credentials and deploy to fetch granted scopes.');
33
+ return;
34
+ }
35
+ $scopeSpinner.show();
36
+ $.getJSON(`uos/oauth-scopes/${configId}`)
37
+ .done((data) => {
38
+ if (data && data.scope) {
39
+ updateDisplay(data.scope);
40
+ }
41
+ else {
42
+ updateDisplay('The client did not return a scope string.');
43
+ }
44
+ })
45
+ .fail((xhr) => {
46
+ const msg = xhr?.responseJSON?.error || xhr.statusText || 'Could not read scopes';
47
+ updateDisplay(msg);
48
+ })
49
+ .always(() => {
50
+ $scopeSpinner.hide();
51
+ });
52
+ };
53
+ $refresh.on('click', function (evt) {
54
+ evt.preventDefault();
55
+ fetchScopes();
56
+ });
57
+ if (node.credentials && node.credentials.clientId) {
58
+ fetchScopes();
59
+ }
60
+ },
18
61
  });
19
62
  </script>
20
63
 
21
- <div class="form-row">
22
- <label for="node-config-input-host"><i class="fa fa-globe"></i> Host</label>
23
- <input type="text" id="node-config-input-host" placeholder="127.0.0.1">
24
- </div>
25
- <div class="form-row">
26
- <label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
27
- <input type="number" id="node-config-input-port">
28
- </div>
29
- <div class="form-row">
30
- <label for="node-config-input-clientName"><i class="fa fa-user"></i> Client Name</label>
31
- <input type="text" id="node-config-input-clientName" placeholder="nodered">
32
- </div>
33
- <div class="form-row">
34
- <label for="node-config-input-clientId"><i class="fa fa-id-card"></i> Client ID</label>
35
- <input type="text" id="node-config-input-clientId">
36
- </div>
37
- <div class="form-row">
38
- <label for="node-config-input-clientSecret"><i class="fa fa-key"></i> Client Secret</label>
39
- <input type="password" id="node-config-input-clientSecret">
40
- </div>
41
- <div class="form-row">
42
- <label for="node-config-input-tokenEndpoint"><i class="fa fa-lock"></i> Token URL</label>
43
- <input type="text" id="node-config-input-tokenEndpoint" placeholder="https://HOST/oauth2/token">
44
- </div>
45
- <div class="form-row">
46
- <label for="node-config-input-scope"><i class="fa fa-list"></i> Scope</label>
47
- <input type="text" id="node-config-input-scope">
48
- </div>
64
+ <script type="text/html" data-template-name="uos-config">
65
+ <div class="form-row">
66
+ <label for="node-config-input-host"><i class="fa fa-globe"></i> Host</label>
67
+ <input type="text" id="node-config-input-host" placeholder="127.0.0.1">
68
+ </div>
69
+ <div class="form-row">
70
+ <label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
71
+ <input type="number" id="node-config-input-port">
72
+ </div>
73
+ <div class="form-row">
74
+ <label for="node-config-input-clientName"><i class="fa fa-user"></i> Client Name</label>
75
+ <input type="text" id="node-config-input-clientName" placeholder="nodered">
76
+ </div>
77
+ <div class="form-row">
78
+ <label for="node-config-input-clientId"><i class="fa fa-id-card"></i> Client ID</label>
79
+ <input type="text" id="node-config-input-clientId">
80
+ </div>
81
+ <div class="form-row">
82
+ <label for="node-config-input-clientSecret"><i class="fa fa-key"></i> Client Secret</label>
83
+ <input type="password" id="node-config-input-clientSecret">
84
+ </div>
85
+ <div class="form-row">
86
+ <label for="node-config-input-scope"><i class="fa fa-list"></i> Scope</label>
87
+ <input type="text" id="node-config-input-scope">
88
+ </div>
89
+ <div class="form-row">
90
+ <label><i class="fa fa-info-circle"></i> Granted scopes</label>
91
+ <div style="display:flex; align-items:center; gap:0.5rem;">
92
+ <button class="red-ui-button" id="uos-scope-refresh" type="button">Refresh</button>
93
+ <span id="uos-scope-display" class="form-tips">Save first to query granted scopes.</span>
94
+ <span id="uos-scope-spinner" class="fa fa-spinner fa-spin" style="display:none;"></span>
95
+ </div>
96
+ </div>
97
+ </script>
98
+
99
+ <script type="text/html" data-help-name="uos-config">
100
+ <p><strong>u-OS Config</strong> stores the shared connection settings for all DataHub nodes.</p>
101
+ <ul>
102
+ <li><strong>Host / Port</strong> – IP and NATS port of the controller (default <code>127.0.0.1:49360</code>).</li>
103
+ <li><strong>Client Name</strong> – Friendly identifier used in NATS connection logs.</li>
104
+ <li><strong>Client ID / Secret</strong> – OAuth credentials from your u-OS Control Center client.</li>
105
+ <li><strong>Token URL</strong> – Optional; defaults to <code>https://&lt;Host&gt;/oauth2/token</code> if left empty.</li>
106
+ <li><strong>Scope</strong> – Space separated scopes; e.g. <code>hub.variables.provide hub.variables.readwrite</code>.</li>
107
+ </ul>
108
+ </script>
@@ -14,7 +14,6 @@ module.exports = function (RED) {
14
14
  this.port = Number(config.port) || 49360;
15
15
  this.clientName = config.clientName || 'nodered';
16
16
  this.scope = config.scope || 'hub.variables.provide hub.variables.readwrite';
17
- this.tokenEndpoint = config.tokenEndpoint || `https://${this.host}/oauth2/token`;
18
17
  this.clientId = this.credentials.clientId;
19
18
  this.clientSecret = this.credentials.clientSecret;
20
19
  this.tokenInfo = null;
@@ -37,7 +36,8 @@ module.exports = function (RED) {
37
36
  scope: this.scope,
38
37
  });
39
38
  const basic = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
40
- const res = await fetch(this.tokenEndpoint, {
39
+ const tokenEndpoint = `https://${this.host}/oauth2/token`;
40
+ const res = await fetch(tokenEndpoint, {
41
41
  method: 'POST',
42
42
  headers: {
43
43
  Authorization: `Basic ${basic}`,
@@ -57,6 +57,7 @@ module.exports = function (RED) {
57
57
  this.tokenInfo = {
58
58
  token: json.access_token,
59
59
  expiresAt: now + ((json.expires_in || 3600) * 1000),
60
+ grantedScope: json.scope || this.scope,
60
61
  };
61
62
  return this.tokenInfo.token;
62
63
  };
@@ -80,6 +81,11 @@ module.exports = function (RED) {
80
81
  return this.nc;
81
82
  };
82
83
 
84
+ this.getGrantedScopes = async () => {
85
+ await this.getToken();
86
+ return this.tokenInfo?.grantedScope || '';
87
+ };
88
+
83
89
  this.fetchProviders = async () => {
84
90
  const token = await this.getToken();
85
91
  const res = await fetch(`https://${this.host}/datahub/v1/providers`, {
@@ -162,6 +168,20 @@ module.exports = function (RED) {
162
168
  res.status(500).json({ error: err.message });
163
169
  }
164
170
  });
171
+ RED.httpAdmin.get('/uos/oauth-scopes/:id', async (req, res) => {
172
+ const node = RED.nodes.getNode(req.params.id);
173
+ if (!node) {
174
+ res.status(404).json({ error: 'config not found' });
175
+ return;
176
+ }
177
+ try {
178
+ const scope = await node.getGrantedScopes();
179
+ res.json({ scope });
180
+ }
181
+ catch (err) {
182
+ res.status(500).json({ error: err.message });
183
+ }
184
+ });
165
185
  }
166
186
 
167
187
  RED.nodes.registerType('uos-config', UosConfigNode, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Node-RED nodes for u-OS Data Hub via NATS",
5
5
  "repository": {
6
6
  "type": "git",