0

I'm trying to make a web UI for the Philips Hue API. This involves sending http requests over the users' private network to communicate with their hue bridge device. Is this possible? I'm currently trying to do it on a page load using axios, but it doesn't seem to execute on the client side. This would likely be a giant security vulnerability if possible, so I'm thinking it's not. Just wondering if anyone has experience trying to do something like this.

An example of my sveltekit code:

export const load: PageLoad = (async () => {
  try {
    const headers = new AxiosHeaders();
    headers.set('hue-application-key', BRIDGE_USERNAME);


    const res = await axios.request({
      url: `https://${BRIDGE_IP}/clip/v2/resource/light`,
      headers: headers,
      method: 'GET',
      httpsAgent: new https.Agent({
        checkServerIdentity: (hostname: string, cert: PeerCertificate) => {
          // Make sure the peer certificate's common name is equal to
          // the Hue bridgeId that this request is for.
          if (cert.subject.CN === BRIDGE_SERVER_NAME) {
            return undefined;
          } else {
            return new Error('Server identity check failed. CN does not match bridgeId.');
          }
        },
        ca: BRIDGE_SSL_CERT,
      }),
    });

    if (!res.data) {
      throw error(500, 'No data returned from bridge');
    }
    return res.data
  } catch (err) {
    return { error: 'Failed to load lights' };
  }
}) satisfies PageLoad;

I expect this to run on the client and retrieve data from the hue bridge, but it only seems to work server-side.

logging on the backend and not the front

  • What happens when you run it int he client? What exactly isn't working? – asportnoy Feb 13 '23 at 17:17
  • From my understanding sveltekit runs this same code in +page.ts files on the client and server. Not sure about the details of how it handles this. But when I console.log() the res.data it only shows on the server logs. The console in the browser doesn't log any data or errors, but the UI doesn't get the data, so nothing loads on the page. – Andres Delgado Feb 13 '23 at 18:17
  • The backend can't make this call in production since it will be on a completely different network. – Andres Delgado Feb 13 '23 at 18:27

2 Answers2

1

By default, SvelteKit pages are rendered on the server (SSR) for the initial request and in the browser (CSR) for subsequent navigation. Take a look at the documentation here.

It is very likely that you are seeing the output of the server side only because you didn't navigate back and forth to this page after the initial render (refreshing the page is also considered an initial render).

If you want the +page.ts to only run on the client, you can set the ssr variable to false:

export const ssr = false;

This will force the page to render on the client only even for the initial request. At this point, you should see whether the request was successful or not in the browser itself.

Alternatively, if you still want some code to be run on the server for the initial render, you can move your request code to inside the onMount method in the +page.svelte file:

<script lang="ts">
  import { onMount } from 'svelte';

  onMount(async () => {
    // Place your request here
  });
</script>
carlosV2
  • 1,167
  • 6
  • 15
  • Thank you for your thoughtful response! I've disabled SSR for that page, but get the same behavior on the client side. No error in the console and the data doesn't load. – Andres Delgado Feb 14 '23 at 03:15
  • Ok. Just to throw silly reasons out: is it possible you have warning/errors disabled in your browser's console? If you go to the Network tab in Developer Tools, can you see the request being made there (even if it fails)? If the data does not load, what does? What happens if you `console.log` the `res` variable? – carlosV2 Feb 14 '23 at 03:29
  • Ok, so the network tab wasn't showing the request, but when I print out the error being caught, I now see ```Error: Module "https" has been externalized for browser compatibility. Cannot access "https.Agent" in client code. at Object.get (__vite-browser-external:https:3:11) at load (+page.ts:46:29) at load_node (client.js?t=1676477992807&v=890cd52d:601:40)``` So it looks like you can't set the http agent on the client-side version of the code. This seems like a security measure. – Andres Delgado Feb 15 '23 at 16:21
  • Unfortunately, I'm not familiar with `axios`. However, I can read on their documentation that the `httpsAgent` seems to be [for node.js only](https://axios-http.com/docs/req_config). This suggest to me that, indeed, it is not a browser-compatible feature. So, the question is, do you really need this? Perhaps it is a security feature but, just to completely rule this out, if you comment the `httpsAgent` parameter, does it work? What request exactly do you need to do? Maybe I can help you to rewrite it with `fetch` instead if you are interested. – carlosV2 Feb 15 '23 at 20:57
  • I really appreciate your continued support, @carlosV2 ! Unfortunately, I do need to provide the custom http agent because the hue bridge v2 api communicates over https and I need to provide the Signify private CA Certificate. However, what if I use the older api that doesn't use http? Not sure yet. I'll see if I can make a regular fetch, not providing the http agent, to the v1 api and report back. – Andres Delgado Feb 16 '23 at 13:31
  • Ah, bummer. Maybe you can make your users to install the certificate in the browser first... maybe this will allow the browser to validate the certificate on its own. To be honest, at this point, it is purely speculative guesses. Anyway, good luck! – carlosV2 Feb 16 '23 at 13:43
  • That's a great idea, @carlosV2! I could first try just adding the certificate to my browser and testing. – Andres Delgado Feb 16 '23 at 21:43
0

When making a client-side https request to the Hue Bridge on the user's network, it failed because it didn't recognize the certificate. After downloading the certificate from the Hue API documentation and installing it on my browser, I got errors about not being able to verify the server name.

When making the request on the sever side, I was able to get around this by providing a callback function for verifying the server name in a custom http agent. Since this option isn't available on the client-side fetch or axios request methods, I abandoned the idea of using the v2 API and switched to using v1, which sets headers to allow for CORS.

Here is the code I ended up with:

export const ssr = false;

export const load: PageLoad = (async () => {
  try {
    const opts = {
      method: 'GET',
    };

    const res = await fetch(`http://${PUBLIC_BRIDGE_IP}/api/${PUBLIC_BRIDGE_USERNAME}/lights`, opts);

    const data: HueLights = await res.json();
    if (!data) {
      throw error(500, 'No data returned from bridge');
    }
    const lights = Object.keys(data).map((key): Light => {
      const light: HueLight = data[key];
      return {
        id: key,
        uniqueId: light?.uniqueid,
        name: light?.name,
        type: light?.config?.archetype,
        color: {
          xy: {
            x: light?.state?.xy[0],
            y: light?.state?.xy[1],
          },
        },
        on: light?.state?.on,
        dimming: {
          brightness: light?.state?.bri,
          minDimLevel: light?.capabilities?.control?.mindimlevel,
        },
      };
    });
    return { lights };
  } catch (err) {
    return { error: 'Failed to load lights' };
  }
}) satisfies PageLoad;