Custom Message Handling in the Omniverse on DGX Cloud Portal Sample to Support Authentication Flows#

For the public version of the Portal Sample file, see useStream.ts on GitHub.

Adapted Version with NavVis IVION Authentication#

Adapted useStream.ts with NavVis IVION support#
      import { hideNotification, notifications } from "@mantine/notifications";
      import {
              AppStreamer,
              DirectConfig,
              eAction,
              eStatus,
              LogFormat,
              LogLevel,
              StreamEvent,
              StreamType,
      } from "@nvidia/omniverse-webrtc-streaming-library";
      import { useCallback, useEffect, useRef, useState } from "react";
      import { Config } from "../providers/ConfigProvider";
      import { StreamingApp } from "../state/Apps";
      import { useConfig } from "./useConfig";
      import useError from "./useError";
      import useStreamStart, {
              showStreamWarning,
              streamStartNotification,
      } from "./useStreamStart";

      export interface UseStreamOptions {
              app: StreamingApp;
              sessionId: string;
              videoElementId?: string;
              audioElementId?: string;
      }

      export interface UseStreamResult {
              loading: boolean;
              error: Error | string;
              terminate: () => Promise<void>;
      }

      export default function useStream({
              app,
              sessionId,
              videoElementId = "stream-video",
              audioElementId = "stream-audio",
      }: UseStreamOptions): UseStreamResult {
              const config = useConfig();
              const [loading, setLoading] = useState(false);
              const [error, setError] = useError();

              const initialized = useRef(false);

              const { mutateAsync: startNewSession } = useStreamStart(app.id);
              const startNewSessionRef = useRef(startNewSession);
              startNewSessionRef.current = startNewSession;

              useEffect(() => {
                      if (!sessionId) {
                              return;
                      }

                      if (initialized.current) {
                              return;
                      }

                      initialized.current = true;

                      setLoading(true);
                      setError("");

                      function onUpdate(message: StreamEvent) {
                              console.log("onUpdate", message);
                      }

                      function onStart(message: StreamEvent) {
                              console.log("onStart", message);

                              if (message.action === eAction.start) {
                                      if (message.status === eStatus.success) {
                                              const video = document.getElementById(
                                                      videoElementId,
                                              ) as HTMLVideoElement;

                                              video.play().catch((error) => {
                                                      setError(error as Error);
                                              });

                                              setLoading(false);
                                              hideNotification(streamStartNotification);
                                      } else if (message.status === eStatus.error) {
                                              setError(message.info || "Unknown error.");
                                              setLoading(false);
                                      } else if (message.status === eStatus.warning) {
                                              showStreamWarning();
                                      }
                              }
                      }

                      function onStop(message: StreamEvent) {
                              console.log("onStop", message);
                      }

                      function onTerminate(message: StreamEvent) {
                              console.log("onTerminate", message);
                      }

                      function onStreamStats(message: StreamEvent) {
                              console.log("onStreamStats", message);
                      }

                      // Prefix used for local storage items specific to the omni.pointcloud.potree2 extension
                      const POTREE2_STORAGE_PREFIX: string = "omni.pointcloud.potree2:";

                      // Removes expired local storage items that were received from the omni.pointcloud.potree2
                      // extension. When "clear_all" is true, all of those items will be removed, independent of their
                      // expiration status.
                      function expirePotree2Storage(clear_all: boolean = false): void {
                              // Retrieve all keys that have the prefix
                              var keys: string[] = [];
                              for (var i=0; i < localStorage.length; i++) {
                                      const key = localStorage.key(i);
                                      if (key != null && key.startsWith(POTREE2_STORAGE_PREFIX)) {
                                              keys.push(key);
                                      }
                              }

                              for (const key of keys) {
                                      if (clear_all) {
                                              console.info("Removing storage entry '" + key + "'");
                                              localStorage.removeItem(key);
                                      }
                                      else {
                                              const data_json = localStorage.getItem(key)
                                              if (data_json != null) {
                                                      const data = JSON.parse(data_json);
                                                      if ("expiration_time" in data) {
                                                              const now = Date.now() / 1000; // seconds since epoch
                                                              if (now >= data.expiration_time) {
                                                                      console.info("Removing expired storage entry '" + key + "'");
                                                                      localStorage.removeItem(key);
                                                              }
                                                      }
                                              }
                                      }
                              }
                      }

                      // Handles messages sent by the omni.pointcloud.potree2 extension.
                      // Returns true if the message was processed, false otherwise.
                      function handlePotree2Event (message: unknown): boolean {
                              if (!message || !("event_type" in message && "payload" in message)) {
                                      return false;
                              }

                              // open_url: Open the requested URL in a new browser tab.
                              // Note: This may trigger the browser's pop-up blocker under certain conditions.
                              if (message.event_type === "omni.pointcloud.potree2@open_url") {
                                      if ("url" in message.payload) {
                                              const url = message.payload.url;
                                              console.info(message.event_type + ": Opening URL in new tab:", url);
                                              window.open(url, '_blank', 'noopener,noreferrer');
                                      }
                                      return true;
                              }

                              // ping: Answer with "ping_result", letting Kit know that a client is connected.
                              if (message.event_type === "omni.pointcloud.potree2@ping") {
                                      const answer = {
                                              event_type: message.event_type + "_result",
                                              payload: true
                                      };
                                      console.info(message.event_type + ": Sending answer.");
                                      AppStreamer.sendMessage(JSON.stringify(answer));
                                      return true;
                              }

                              // store_data: Store the received value in the browser's local storage.
                              // Expiration will be handled by expirePotree2Storage().
                              //
                              // Note: Instead of using local storage here, the data could also be passed on to another web
                              // service for storage.
                              if (message.event_type === "omni.pointcloud.potree2@store_data") {
                                      var key = message.payload.key;
                                      if (key != "") {
                                              const data = {
                                                      value: message.payload.value,
                                                      expiration_time: message.payload.expiration_time
                                              }
                                              localStorage.setItem(POTREE2_STORAGE_PREFIX + key, JSON.stringify(data));
                                              console.info(message.event_type + ": Stored data for key '" + POTREE2_STORAGE_PREFIX + key + "'");
                                      }
                                      return true;
                              }

                              // get_data: Return data from local storage that was previously sent with "store_data".
                              // Answer with "get_data_result" and the retrieved value.
                              if (message.event_type === "omni.pointcloud.potree2@get_data") {
                                      // Remove expired items
                                      expirePotree2Storage();

                                      var key = message.payload.key;
                                      if (key != "") {
                                              const data_json = localStorage.getItem(POTREE2_STORAGE_PREFIX + key)
                                              var value = null;
                                              if (data_json != null) {
                                                      const data = JSON.parse(data_json)
                                                      if ("value" in data) {
                                                              // Valid item was found
                                                              value = data.value;

                                                              // Check its expiration again
                                                              if ("expiration_time" in data) {
                                                                      const now = Date.now() / 1000; // seconds since epoch
                                                                      if (now >= data.expiration_time) {
                                                                              console.info(message.event_type + ": Requested storage entry is expired")
                                                                              localStorage.removeItem(POTREE2_STORAGE_PREFIX + key);
                                                                              value = null
                                                                      }
                                                              }
                                                      }
                                              }

                                              // Prepare answer; value will be null if no stored item was found
                                              const answer = {
                                                      event_type: message.event_type + "_result",
                                                      payload: {key: key, value: value}
                                              };
                                              console.info(message.event_type + ": Sending " + (value == null ? "empty " : "")
                                                                                                     + "answer for key '" + POTREE2_STORAGE_PREFIX + key + "'");
                                              AppStreamer.sendMessage(JSON.stringify(answer));
                                      }
                                      return true;
                              }

                              return false;
                      }

                      function onCustomEvent(message: unknown) {
                              console.log("onCustomEvent", message);

                              if (handlePotree2Event(message)) {
                                      return;
                              }
                      }

                      const params = createStreamConfig(app, sessionId, config);

                      async function connect() {
                              try {
                                      const sessionExists = await checkSession(sessionId, config);
                                      if (!sessionExists) {
                                              notifications.show({
                                                      id: streamStartNotification,
                                                      message:
                                                              "This session is no longer available, starting a new streaming session...",
                                                      loading: true,
                                                      autoClose: 30000,
                                              });

                                              try {
                                                      return await startNewSessionRef.current();
                                              } catch (error) {
                                                      setError(error as Error);
                                                      setLoading(false);
                                              }
                                      }

                                      await AppStreamer.connect({
                                              streamSource: StreamType.NVCF,
                                              logLevel: LogLevel.DEBUG,
                                              logFormat: LogFormat.TEXT,
                                              streamConfig: {
                                                      videoElementId,
                                                      audioElementId,
                                                      maxReconnects: 3,
                                                      nativeTouchEvents: true,
                                                      ...params,
                                                      onUpdate,
                                                      onStart,
                                                      onStop,
                                                      onTerminate,
                                                      onStreamStats,
                                                      onCustomEvent,
                                              },
                                      });
                              } catch (error) {
                                      setError(
                                              "info" in (error as StreamEvent)
                                                      ? (error as StreamEvent).info
                                                      : (error as Error),
                                      );
                                      setLoading(false);
                              }
                      }

                      async function start() {
                              console.log("Start streaming...");
                              await connect();
                      }

                      void start();
                      return () => {
                              if (import.meta.env.PROD) {
                                      void AppStreamer.terminate();
                              }
                      };
              }, [app, sessionId, videoElementId, audioElementId, config, setError]);

              const terminate = useCallback(async () => {
                      try {
                              await AppStreamer.terminate(true);
                      } catch (error) {
                              setError(
                                      "info" in (error as StreamEvent)
                                              ? (error as StreamEvent).info
                                              : (error as Error),
                              );
                              console.error("Error terminating stream:", error);
                      }
              }, [setError]);

              return {
                      loading,
                      error,
                      terminate,
              };
      }

      async function checkSession(
              sessionId: string,
              config: Config,
      ): Promise<boolean> {
              const url = createStreamURL(sessionId, config);
              url.pathname += "/sign_in";

              try {
                      const response = await fetch(url, { method: "HEAD" });
                      return response.ok;
              } catch (error) {
                      console.error(`Failed to check the current streaming session:`, error);
                      return false;
              }
      }

      /**
             * Creates URL parameters for streaming the application from NVCF.
             * Returns URLSearchParams instance with values that must be passed to streamConfig object in
             * the `urlLocation.search` field.
             *
             * @param app
             * @param sessionId
             * @param config
             * @returns {URLSearchParams}
             */
      function createStreamConfig(
              app: StreamingApp,
              sessionId: string,
              config: Config,
      ): Partial<DirectConfig> {
              const params: DirectConfig = {
                      width: 1920,
                      height: 1080,
                      fps: 60,
                      mic: false,
                      cursor: "free",
                      autoLaunch: true,

                      // Specifies that the default streaming endpoint must not be used.
                      // Enables signaling parameters for the component.
                      server: "",
              };

              // If specified, enables the private endpoint created in Azure
              if (app.mediaServer) {
                      params.mediaServer = app.mediaServer;
                      if (app.mediaPort) {
                              params.mediaPort = app.mediaPort;
                      }
              }

              const signalingURL = createStreamURL(sessionId, config);
              params.signalingServer = signalingURL.hostname;
              params.signalingPort = signalingURL.port
                      ? Number(signalingURL.port)
                      : signalingURL.protocol === "https:"
                              ? 443
                              : 80;
              params.signalingPath = signalingURL.pathname;
              params.signalingQuery = signalingURL.searchParams;
              return params;
      }

      /**
             * Constructs a URL object for streaming the specified NVCF function.
             *
             * @param sessionId
             * @param config
             * @returns {URL}
             */
      function createStreamURL(sessionId: string, config: Config): URL {
              let backend = config.endpoints.backend;
              if (!backend.endsWith("/")) {
                      backend += "/";
              }

              return new URL(`./sessions/${sessionId}`, backend);
      }

Usage Notes#

  • Paste this adapted hook into your Portal Sample codebase (for example web/src/hooks/useStream.ts) to add NavVis IVION-related handling and omni.pointcloud.potree2 extension message processing.

  • Keep a copy of the public upstream file for reference: useStream.ts on GitHub.