felixhageloh / uebersicht

ˈyːbɐˌzɪçt
GNU General Public License v3.0
4.55k stars 166 forks source link

how does one read a file in a jsx widget? #526

Open kvaradhan3 opened 3 months ago

kvaradhan3 commented 3 months ago

I am not a react/node/java coder at all and am muddling through by example here, so please bear with me if this is a stupid question.

I am trying to write a weather app, index.jsx, and dont want to encode the API key in the file, checked into github.

What would be the best way to do this?

  1. I tried this, which I cam across in a few places on stackoverflow (to see if the read succeeds:
var fs = require('fs'),
    path = require('path'),
    filePath = path.join("/Users/kvaradhan", keyFile);

fs.readFile(filePath, {encoding: 'utf-8'}, function(err, data) {
    if (!err) {
    console.log('received data: ' + data);
    } else {
    console.log(err);
    }
});

It didn't, i get this on the console log:

TypeError: fs.readFile is not a function. (In 'fs.readFile(filePath, {
  encoding: 'utf-8'
}, function (err, data) {
  if (!err) {
    console.log('received data: ' + data);
  } else {
    console.log(err);
  }
})', 'fs.readFile' is undefined)
  1. I also tried this (which now probably reflects my skill level)
    
    const passCode = () =>
      run(`cat /Users/kvaradhan/${keyFile}`).then((output) => {
      console.log("reading " + `cat /Users/kvaradhan/${keyFile}`)
      console.log(output);
      return output;
      })

console.log(passCode)


and this is on the console log:

() => (0, _uebersicht.run)("cat /Users/kvaradhan/".concat(keyFile)).then(output => { console.log("reading " + "cat /Users/kvaradhan/".concat(keyFile)); console.log(output); return output; })



Q:  Is there a good working example of how to do this?  or how I might proceed?

Thanks,

Kannan
00dani commented 1 month ago

Hi! The most important thing you need to understand about programming in JavaScript is that I/O operations, like reading a file or making a web request, are asynchronous - when your code makes the call to read a file or whatever, it cannot pause to wait for the result. Instead, you provide some additional code describing what to do when the result does arrive. For example, fs.readFile() does not return the contents of the file - instead, it calls the function(err, data) you provided once the file has been read.

This arrangement can be a pain to work with directly, so there are two layers of helpfulness on top of the basic asynchronous operation of the language. The first is called promises: when you call run('cat /Users/kvaradhan/' + keyFile), a promise is immediately returned, which represents the asynchronous operation you're waiting for. You can return that promise yourself and pass it around to other functions, but if you want to "wait" for the asynchronous operation to finish, you call the promise's .then() method and give it a function to run once the result is available. Importantly, this feature allows you to chain multiple asynchronous operations together, so you can do things like this:

function doThreeThings() {
  return doSomething()
    .then(result => doAnotherThingWith(result))
    .then(anotherResult => doThirdThingWith(anotherResult));
}

In order to run those three operations in sequence and get the result from the third.

However, promises can still be verbose and inconvenient for some kinds of code. So the second layer of helpfulness is called an async function. If you put the async keyword before a function, then you can use the await keyword inside that function to "unwrap" a promise. Behind the scenes this works in the same way as the previous approach, calling .then() for each promise to chain them together, but it can be much friendlier to read:

async function doThreeThings() {
  var result = await doSomething();
  var anotherResult = await doAnotherThingWith(result);
  return doThirdThingWith(anotherResult);
}

Using async functions also means that normal JavaScript control flow, like if and try, will work as expected with asynchronous calls. await will throw an exception if the asynchronous operation failed, for example.


So, with that context covered, let's talk about your situation. The reason require('fs') didn't work is that fs is a module provided by Node.js, a JavaScript engine designed for running servers. Übersicht widgets do not run in Node.js - they essentially run in a Safari browser window - so Node.js features are not available to your widget code. You need a different way to read files.

Fortunately, run('cat whatever') is a valid way to read files from Übersicht widgets. The run() function, provided by Übersicht itself, asynchronously runs a shell command and returns a promise for its output. This will work:

run(`cat /Users/kvaradhan/${keyFile}`).then(apiKey => console.log(apiKey));

Of course, you probably want to use the API key once you have it. I don't know what your weather API looks like, so I can't give you an exact example, but here's a really basic working example of making a simple HTTP call and displaying its results:

import { run } from 'uebersicht';
const keyPath = '~/my-api-key-file';
const keyAsync = run(`cat ${keyPath}`);

export const refreshFrequency = 1000 * 60; // run once per minute
export const command = async () => {
  const key = await keyAsync;
  const response = await fetch('https://httpbin.org/headers', {headers: {Authorization: `Bearer ${key}`}})
  return response.json();
};

export const className = {
  bottom: 0, right: 0,
  width: 800, height: 300,
  backgroundColor: 'white',
  whiteSpace: 'pre-wrap',
};

export const render = ({output}) => <code>{JSON.stringify(output.headers, null, 2)}</code>;

Hopefully that's enough to get you going! :sparkling_heart: