@aaqu/fromcubes-portal-react 0.1.0-alpha.3 → 0.1.0-alpha.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.
@@ -45,7 +45,7 @@
45
45
  var libContent = [
46
46
  "declare var React: any;",
47
47
  "declare var ReactDOM: any;",
48
- "declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void };",
48
+ "declare function useNodeRed(): { data: any; send: (payload: any, topic?: string) => void; user: { userId?: string; userName?: string; username?: string; email?: string; role?: string; groups?: any[] } | null };",
49
49
  ].join("\n");
50
50
  console.log(PREFIX, "addExtraLib globals.d.ts");
51
51
  jsDef.addExtraLib(libContent, "file:///globals.d.ts");
@@ -660,7 +660,7 @@
660
660
  <div class="form-row" style="margin-bottom:0;">
661
661
  <div style="font-size:11px;opacity:.6;margin-bottom:4px;">
662
662
  <code>useNodeRed()</code> &rarr;
663
- <code>{ data, send }</code> &nbsp;|&nbsp; Components from
663
+ <code>{ data, send, user }</code> &nbsp;|&nbsp; Components from
664
664
  <code>fc-portal-component</code> nodes auto-imported &nbsp;|&nbsp;
665
665
  Must export <code>&lt;App /&gt;</code> &nbsp;|&nbsp;
666
666
  <strong>Transpiled server-side at deploy</strong>
@@ -711,6 +711,31 @@
711
711
  </div>
712
712
  </div>
713
713
  </div>
714
+ <!-- ── Tab: Auth ── -->
715
+ <div id="fc-tab-auth" class="fc-tab-pane" style="display:none;">
716
+ <div class="form-row">
717
+ <label style="width:auto;">
718
+ <input
719
+ type="checkbox"
720
+ id="node-input-portalAuth"
721
+ style="width:auto;margin:0 8px 0 0;vertical-align:middle;"
722
+ />
723
+ Enable Portal Auth headers
724
+ </label>
725
+ </div>
726
+ <div class="form-row" style="padding-left:4px;">
727
+ <div style="font-size:11px;opacity:.6;line-height:1.5;">
728
+ Read <code>X-Portal-*</code> headers set by Nginx proxy and expose user data
729
+ via <code>useNodeRed()</code>.<br><br>
730
+ When enabled:<br>
731
+ &bull; <code>useNodeRed()</code> returns <code>{ data, send, user }</code> where
732
+ <code>user</code> contains <code>userId</code>, <code>userName</code>,
733
+ <code>username</code>, <code>email</code>, <code>role</code>, <code>groups</code><br>
734
+ &bull; Messages from WebSocket include <code>msg._client</code> with user data<br><br>
735
+ Requires <code>@aaqu/node-red-dashboard-2-portal-auth</code> Nginx setup.
736
+ </div>
737
+ </div>
738
+ </div>
714
739
  </div>
715
740
 
716
741
  <input type="hidden" id="node-input-componentCode" />
@@ -758,6 +783,7 @@
758
783
  pageTitle: { value: "Portal" },
759
784
  componentCode: { value: STARTER },
760
785
  customHead: { value: "" },
786
+ portalAuth: { value: false },
761
787
  },
762
788
  inputs: 1,
763
789
  outputs: 1,
@@ -798,6 +824,7 @@
798
824
  fcTabs.addTab({ id: "fc-tab-jsx", label: "JSX" });
799
825
  fcTabs.addTab({ id: "fc-tab-props", label: "Properties" });
800
826
  fcTabs.addTab({ id: "fc-tab-head", label: "Head HTML" });
827
+ fcTabs.addTab({ id: "fc-tab-auth", label: "Auth" });
801
828
  fcTabs.activateTab("fc-tab-jsx");
802
829
 
803
830
  // URL hint
@@ -1048,6 +1075,17 @@ const { data, send } = useNodeRed();
1048
1075
  by their component name.
1049
1076
  </p>
1050
1077
 
1078
+ <h3>Portal Auth</h3>
1079
+ <p>
1080
+ When enabled in the Auth tab, reads <code>X-Portal-*</code> headers set by
1081
+ an Nginx proxy (via <code>@aaqu/node-red-dashboard-2-portal-auth</code>) and
1082
+ exposes user data:
1083
+ </p>
1084
+ <ul>
1085
+ <li><code>useNodeRed()</code> returns <code>{ data, send, user }</code></li>
1086
+ <li>Messages from WebSocket include <code>msg._client</code> with user info</li>
1087
+ </ul>
1088
+
1051
1089
  <h3>Custom Head HTML</h3>
1052
1090
  <p>
1053
1091
  Inject CDN links, fonts, or extra stylesheets into
@@ -134,6 +134,23 @@ module.exports = function (RED) {
134
134
  );
135
135
  }
136
136
 
137
+ function extractPortalUser(headers) {
138
+ const user = {};
139
+ if (headers["x-portal-user-id"]) user.userId = headers["x-portal-user-id"];
140
+ if (headers["x-portal-user-name"]) user.userName = headers["x-portal-user-name"];
141
+ if (headers["x-portal-user-username"]) user.username = headers["x-portal-user-username"];
142
+ if (headers["x-portal-user-email"]) user.email = headers["x-portal-user-email"];
143
+ if (headers["x-portal-user-role"]) user.role = headers["x-portal-user-role"];
144
+ if (headers["x-portal-user-groups"]) {
145
+ try {
146
+ user.groups = JSON.parse(headers["x-portal-user-groups"]);
147
+ } catch (_) {
148
+ user.groups = headers["x-portal-user-groups"];
149
+ }
150
+ }
151
+ return Object.keys(user).length > 0 ? user : null;
152
+ }
153
+
137
154
  function removeRoute(router, path) {
138
155
  if (!router || !router.stack) return;
139
156
  router.stack = router.stack.filter(
@@ -196,6 +213,7 @@ module.exports = function (RED) {
196
213
  const componentCode = config.componentCode || "";
197
214
  const pageTitle = config.pageTitle || "Portal";
198
215
  const customHead = config.customHead || "";
216
+ const portalAuth = config.portalAuth === true;
199
217
 
200
218
  // State
201
219
  const clients = new Set();
@@ -238,7 +256,8 @@ module.exports = function (RED) {
238
256
  const send = React.useCallback((payload, topic) => {
239
257
  window.__NR.send(payload, topic);
240
258
  }, []);
241
- return { data, send };
259
+ const user = window.__NR._user || null;
260
+ return { data, send, user };
242
261
  }`,
243
262
  "",
244
263
  "// ── Library components ──",
@@ -278,6 +297,7 @@ module.exports = function (RED) {
278
297
  pageTitle,
279
298
  wsPath,
280
299
  customHead,
300
+ portalAuth,
281
301
  };
282
302
  }
283
303
 
@@ -305,6 +325,7 @@ module.exports = function (RED) {
305
325
  return;
306
326
  }
307
327
  const cssHash = await state.cssHashReady;
328
+ const user = state.portalAuth ? extractPortalUser(_req.headers) : null;
308
329
  res
309
330
  .type("text/html")
310
331
  .send(
@@ -314,6 +335,7 @@ module.exports = function (RED) {
314
335
  state.wsPath,
315
336
  state.customHead,
316
337
  cssHash,
338
+ user,
317
339
  ),
318
340
  );
319
341
  });
@@ -351,11 +373,14 @@ module.exports = function (RED) {
351
373
  RED.server.on("upgrade", onUpgrade);
352
374
  upgradeHandlers[nodeId] = onUpgrade;
353
375
 
354
- wsServer.on("connection", (ws) => {
376
+ wsServer.on("connection", (ws, request) => {
355
377
  if (isClosing) {
356
378
  ws.close();
357
379
  return;
358
380
  }
381
+ if (portalAuth) {
382
+ ws._portalUser = extractPortalUser(request.headers);
383
+ }
359
384
  clients.add(ws);
360
385
  updateStatus();
361
386
 
@@ -368,10 +393,14 @@ module.exports = function (RED) {
368
393
  try {
369
394
  const msg = JSON.parse(raw.toString());
370
395
  if (msg.type === "output") {
371
- node.send({
396
+ const out = {
372
397
  payload: msg.payload,
373
398
  topic: msg.topic || "",
374
- });
399
+ };
400
+ if (portalAuth && ws._portalUser) {
401
+ out._client = ws._portalUser;
402
+ }
403
+ node.send(out);
375
404
  }
376
405
  } catch (e) {
377
406
  node.warn("Bad WS message: " + e.message);
@@ -534,7 +563,7 @@ module.exports = function (RED) {
534
563
 
535
564
  // ── Page builders ─────────────────────────────────────────────
536
565
 
537
- function buildPage(title, transpiledJs, wsPath, customHead, cssHash) {
566
+ function buildPage(title, transpiledJs, wsPath, customHead, cssHash, user) {
538
567
  return `<!DOCTYPE html>
539
568
  <html lang="en">
540
569
  <head>
@@ -561,14 +590,14 @@ body{font-family:system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#e0
561
590
  <div id="__cs" class="err">disconnected</div>
562
591
  <script>
563
592
  window.__NR={
564
- _ws:null,_listeners:new Set(),_lastData:null,_retries:0,_wasConnected:false,
593
+ _ws:null,_listeners:new Set(),_lastData:null,_retries:0,_wasConnected:false,_user:${user ? escScript(JSON.stringify(user)) : 'null'},
565
594
  connect(){
566
595
  const p=location.protocol==='https:'?'wss:':'ws:';
567
596
  const ws=new WebSocket(p+'//'+location.host+'${wsPath}');
568
597
  this._ws=ws;
569
598
  const s=document.getElementById('__cs');
570
599
  ws.onopen=()=>{
571
- if(this._wasConnected){location.reload();return;}
600
+ if(this._wasConnected){s.textContent='connected';s.className='ok';this._retries=0;return;}
572
601
  s.textContent='connected';s.className='ok';this._retries=0;this._wasConnected=true;
573
602
  };
574
603
  ws.onmessage=(e)=>{
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@aaqu/fromcubes-portal-react",
3
- "version": "0.1.0-alpha.3",
4
- "description": "Fromcubes Portal - React for Node-RED",
3
+ "version": "0.1.0-alpha.5",
4
+ "description": "Fromcubes Portal - React for Node-RED with Tailwind CSS and auto complete",
5
5
  "keywords": [
6
6
  "node-red",
7
- "react",
8
7
  "dashboard",
9
8
  "portal",
10
9
  "jsx",