forked from
chadtmiller.com/slices-teal-relay
Teal.fm frontend powered by slices.network
tealfm-slices.wisp.place
tealfm
slices
1import { StrictMode, Suspense } from "react";
2import { createRoot } from "react-dom/client";
3import { BrowserRouter, Routes, Route } from "react-router-dom";
4import "./index.css";
5import App from "./App.tsx";
6import Profile from "./Profile.tsx";
7import TopTracks from "./TopTracks.tsx";
8import TopAlbums from "./TopAlbums.tsx";
9import LoadingFallback from "./LoadingFallback.tsx";
10import { RelayEnvironmentProvider } from "react-relay";
11import {
12 Environment,
13 Network,
14 type FetchFunction,
15 Observable,
16 type SubscribeFunction,
17 type GraphQLResponse,
18} from "relay-runtime";
19import { createClient } from "graphql-ws";
20
21const HTTP_ENDPOINT =
22 "https://api.slices.network/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a";
23
24const WS_ENDPOINT =
25 "wss://api.slices.network/graphql/ws?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a";
26
27const fetchGraphQL: FetchFunction = async (request, variables) => {
28 const resp = await fetch(HTTP_ENDPOINT, {
29 method: "POST",
30 headers: { "Content-Type": "application/json" },
31 body: JSON.stringify({ query: request.text, variables }),
32 });
33 if (!resp.ok) {
34 throw new Error("Response failed.");
35 }
36 return await resp.json();
37};
38
39const wsClient = createClient({
40 url: WS_ENDPOINT,
41 retryAttempts: 5,
42 shouldRetry: () => true,
43 on: {
44 connected: () => {
45 console.log("WebSocket connected!");
46 },
47 error: (error) => {
48 console.error("WebSocket error:", error);
49 },
50 closed: (event) => {
51 console.log("WebSocket closed:", event);
52 },
53 },
54});
55
56const subscribe: SubscribeFunction = (operation, variables) => {
57 return Observable.create((sink) => {
58 if (!operation.text) {
59 sink.error(new Error("Missing operation text"));
60 return;
61 }
62
63 return wsClient.subscribe(
64 {
65 operationName: operation.name,
66 query: operation.text,
67 variables,
68 },
69 {
70 next: (data) => {
71 if (data.data !== null && data.data !== undefined) {
72 sink.next({ data: data.data } as GraphQLResponse);
73 }
74 },
75 error: (error) => {
76 console.error("Subscription error:", error);
77 if (error instanceof Error) {
78 sink.error(error);
79 } else if (error instanceof CloseEvent) {
80 sink.error(
81 new Error(`WebSocket closed: ${error.code} ${error.reason}`)
82 );
83 } else {
84 sink.error(new Error(JSON.stringify(error)));
85 }
86 },
87 complete: () => sink.complete(),
88 }
89 );
90 });
91};
92
93const environment = new Environment({
94 network: Network.create(fetchGraphQL, subscribe),
95});
96
97createRoot(document.getElementById("root")!).render(
98 <StrictMode>
99 <BrowserRouter>
100 <RelayEnvironmentProvider environment={environment}>
101 <Suspense fallback={<LoadingFallback />}>
102 <Routes>
103 <Route path="/" element={<App />} />
104 <Route path="/tracks" element={<TopTracks />} />
105 <Route path="/tracks/:period" element={<TopTracks />} />
106 <Route path="/albums" element={<TopAlbums />} />
107 <Route path="/albums/:period" element={<TopAlbums />} />
108 <Route path="/profile/:handle" element={<Profile />} />
109 </Routes>
110 </Suspense>
111 </RelayEnvironmentProvider>
112 </BrowserRouter>
113 </StrictMode>
114);