github.com/pojntfx/hydrapp/hydrapp@v0.0.0-20240516002902-d08759d6ca9f/pkg/generators/App.tsx.tpl (about)

     1  import { ILocalContext, IRemoteContext, Registry } from "@pojntfx/panrpc";
     2  import { JSONParser } from "@streamparser/json-whatwg";
     3  import { useEffect, useState } from "react";
     4  import useAsyncEffect from "use-async";
     5  
     6  class Local {
     7    async ExampleNotification(ctx: ILocalContext, msg: string) {
     8      if ("Notification" in window && Notification.permission !== "granted") {
     9        await Notification.requestPermission();
    10      }
    11  
    12      if ("Notification" in window) {
    13        new Notification(msg);
    14      } else {
    15        alert(msg);
    16      }
    17    }
    18  }
    19  
    20  class Remote {
    21    async ExamplePrintString(ctx: IRemoteContext, msg: string) {
    22      return;
    23    }
    24  
    25    async ExamplePrintStruct(ctx: IRemoteContext, input: any) {
    26      return;
    27    }
    28  
    29    async ExampleReturnError(ctx: IRemoteContext) {
    30      return;
    31    }
    32  
    33    async ExampleReturnString(ctx: IRemoteContext): Promise<string> {
    34      return "";
    35    }
    36  
    37    async ExampleReturnStruct(ctx: IRemoteContext): Promise<any> {
    38      return {};
    39    }
    40  
    41    async ExampleReturnStringAndError(ctx: IRemoteContext): Promise<string> {
    42      return "";
    43    }
    44  
    45    async ExampleCallback(ctx: IRemoteContext) {
    46      return;
    47    }
    48  
    49    async ExampleClosure(
    50      ctx: IRemoteContext,
    51      length: number,
    52      onIteration: (ctx: ILocalContext, i: number, b: string) => Promise<string>
    53    ): Promise<number> {
    54      return 0;
    55    }
    56  }
    57  
    58  const App = () => {
    59    const [clients, setClients] = useState(0);
    60    useEffect(() => console.log(clients, "clients connected"), [clients]);
    61  
    62    const [reconnect, setReconnect] = useState(false);
    63    const [registry] = useState(
    64      new Registry(
    65        new Local(),
    66        new Remote(),
    67  
    68        {
    69          onClientConnect: () => setClients((v) => v + 1),
    70          onClientDisconnect: () =>
    71            setClients((v) => {
    72              if (v === 1) {
    73                setReconnect(true);
    74              }
    75  
    76              return v - 1;
    77            }),
    78        }
    79      )
    80    );
    81  
    82    useAsyncEffect(async () => {
    83      if (reconnect) {
    84        await new Promise((r) => {
    85          setTimeout(r, 100);
    86        });
    87  
    88        setReconnect(false);
    89  
    90        return () => {};
    91      }
    92  
    93      const addr =
    94        new URLSearchParams(window.location.search).get("socketURL") ||
    95        "ws://localhost:1337";
    96  
    97      const socket = new WebSocket(addr);
    98  
    99      socket.addEventListener("error", (e) => {
   100        console.error("Disconnected with error, reconnecting:", e);
   101  
   102        setReconnect(true);
   103      });
   104  
   105      await new Promise<void>((res, rej) => {
   106        socket.addEventListener("open", () => res());
   107        socket.addEventListener("error", rej);
   108      });
   109  
   110      const encoder = new WritableStream({
   111        write(chunk) {
   112          socket.send(JSON.stringify(chunk));
   113        },
   114      });
   115  
   116      const parser = new JSONParser({
   117        paths: ["$"],
   118        separator: "",
   119      });
   120      const parserWriter = parser.writable.getWriter();
   121      const parserReader = parser.readable.getReader();
   122      const decoder = new ReadableStream({
   123        start(controller) {
   124          parserReader
   125            .read()
   126            .then(async function process({ done, value }) {
   127              if (done) {
   128                controller.close();
   129  
   130                return;
   131              }
   132  
   133              controller.enqueue(value?.value);
   134  
   135              parserReader
   136                .read()
   137                .then(process)
   138                .catch((e) => controller.error(e));
   139            })
   140            .catch((e) => controller.error(e));
   141        },
   142      });
   143      socket.addEventListener("message", (m) =>
   144        parserWriter.write(m.data as string)
   145      );
   146      socket.addEventListener("close", () => {
   147        parserReader.cancel();
   148        parserWriter.abort();
   149      });
   150  
   151      registry.linkStream(
   152        encoder,
   153        decoder,
   154  
   155        (v) => v,
   156        (v) => v
   157      );
   158  
   159      console.log("Connected to", addr);
   160  
   161      return () => socket.close();
   162    }, [reconnect]);
   163  
   164    return clients > 0 ? (
   165      <main>
   166        <h1>hydrapp React and panrpc Example</h1>
   167  
   168        <div>
   169          <button
   170            onClick={() =>
   171              registry.forRemotes(async (_, remote) => {
   172                try {
   173                  await remote.ExamplePrintString(
   174                    undefined,
   175                    prompt("String to print")!
   176                  );
   177                } catch (e) {
   178                  alert(JSON.stringify((e as Error).message));
   179                }
   180              })
   181            }
   182          >
   183            Print string
   184          </button>
   185  
   186          <button
   187            onClick={() =>
   188              registry.forRemotes(async (_, remote) => {
   189                try {
   190                  await remote.ExamplePrintStruct(undefined, {
   191                    name: prompt("Name to print")!,
   192                  });
   193                } catch (e) {
   194                  alert(JSON.stringify((e as Error).message));
   195                }
   196              })
   197            }
   198          >
   199            Print struct
   200          </button>
   201  
   202          <button
   203            onClick={() =>
   204              registry.forRemotes(async (_, remote) => {
   205                try {
   206                  await remote.ExampleReturnError(undefined);
   207                } catch (e) {
   208                  alert(JSON.stringify((e as Error).message));
   209                }
   210              })
   211            }
   212          >
   213            Return error
   214          </button>
   215  
   216          <button
   217            onClick={() =>
   218              registry.forRemotes(async (_, remote) => {
   219                try {
   220                  const res = await remote.ExampleReturnString(undefined);
   221  
   222                  alert(JSON.stringify(res));
   223                } catch (e) {
   224                  alert(JSON.stringify((e as Error).message));
   225                }
   226              })
   227            }
   228          >
   229            Return string
   230          </button>
   231  
   232          <button
   233            onClick={() =>
   234              registry.forRemotes(async (_, remote) => {
   235                try {
   236                  const res = await remote.ExampleReturnStruct(undefined);
   237  
   238                  alert(JSON.stringify(res));
   239                } catch (e) {
   240                  alert(JSON.stringify((e as Error).message));
   241                }
   242              })
   243            }
   244          >
   245            Return struct
   246          </button>
   247  
   248          <button
   249            onClick={() =>
   250              registry.forRemotes(async (_, remote) => {
   251                try {
   252                  await remote.ExampleReturnStringAndError(undefined);
   253                } catch (e) {
   254                  alert(JSON.stringify((e as Error).message));
   255                }
   256              })
   257            }
   258          >
   259            Return string and error
   260          </button>
   261  
   262          <button
   263            onClick={() =>
   264              registry.forRemotes(async (_, remote) => {
   265                try {
   266                  await remote.ExampleCallback(undefined);
   267                } catch (e) {
   268                  alert(JSON.stringify((e as Error).message));
   269                }
   270              })
   271            }
   272          >
   273            Get three time notifications (with callback)
   274          </button>
   275  
   276          <button
   277            onClick={() =>
   278              registry.forRemotes(async (_, remote) => {
   279                try {
   280                  await remote.ExampleClosure(undefined, 3, async (_, i, b) => {
   281                    if (
   282                      "Notification" in window &&
   283                      Notification.permission !== "granted"
   284                    ) {
   285                      await Notification.requestPermission();
   286                    }
   287  
   288                    if ("Notification" in window) {
   289                      new Notification(`In iteration ${i} ${b}`);
   290                    } else {
   291                      alert(`In iteration ${i} ${b}`);
   292                    }
   293  
   294                    return "This is from the frontend";
   295                  });
   296                } catch (e) {
   297                  alert(JSON.stringify((e as Error).message));
   298                }
   299              })
   300            }
   301          >
   302            Get three iteration notifications (with closure)
   303          </button>
   304        </div>
   305      </main>
   306    ) : (
   307      "Connecting ..."
   308    );
   309  };
   310  
   311  export default App;
   312