122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
import { BaseKey, LiveEvent, LiveProvider } from "@refinedev/core";
|
|
|
|
/**
|
|
* A spoolman websocket event.
|
|
*/
|
|
interface Event {
|
|
type: "updated" | "deleted" | "added";
|
|
resource: "filament" | "spool" | "vendor";
|
|
date: string;
|
|
payload: {
|
|
id: number;
|
|
[key: string]: unknown;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Converts an API URL to a WebSocket URL.
|
|
* E.g. "https://example.com/api/v1/..." -> "ws://example.com/api/v1/..."
|
|
* or "/api/v1/..." -> "ws://example.com/api/v1/..."
|
|
* @param apiUrl The API URL to convert
|
|
* @returns The WebSocket URL
|
|
*/
|
|
function toWebsocketURL(apiUrl: string) {
|
|
if (apiUrl[0] === "/") {
|
|
// Relative URL, e.g. "/api/v1/..."
|
|
|
|
// Get the current browser URL
|
|
const currentURL = window.location.href;
|
|
|
|
// Split the URL to separate the protocol, host, and path
|
|
const urlParts = currentURL.split("/");
|
|
const host = urlParts[2];
|
|
|
|
// Create the WebSocket URL by adding "ws://" as the protocol and the relative URL as the path
|
|
return `ws://${host}${apiUrl}`;
|
|
} else {
|
|
// Absolute URL, e.g. "https://example.com/api/v1/..."
|
|
|
|
// Replace the protocol with "ws://"
|
|
return apiUrl.replace(/^http/, "ws");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subscribes to a single resource.
|
|
* @param apiUrl The API URL
|
|
* @param channel The channel name, not really used
|
|
* @param resource The resource name
|
|
* @param callback The callback to call when the resource is updated
|
|
* @param id Specific ID to subscribe to, if any. If not specified, subscribes to all IDs.
|
|
* @returns A function to unsubscribe from the resource
|
|
*/
|
|
function subscribeSingle(
|
|
apiUrl: string,
|
|
channel: string,
|
|
resource: string,
|
|
callback: (event: LiveEvent) => void,
|
|
id?: BaseKey
|
|
) {
|
|
// Verify that WebSockets are supported
|
|
if (!("WebSocket" in window)) {
|
|
console.warn("WebSockets are not supported in this browser. Live updates will not be available.");
|
|
return () => {};
|
|
}
|
|
|
|
const websocketURL = id ? toWebsocketURL(`${apiUrl}/${resource}/${id}`) : toWebsocketURL(`${apiUrl}/${resource}`);
|
|
|
|
const ws = new WebSocket(websocketURL);
|
|
ws.onmessage = (message) => {
|
|
const data: Event = JSON.parse(message.data);
|
|
const type = data.type === "added" ? "created" : data.type;
|
|
const date = new Date(data.date);
|
|
|
|
const liveEvent: LiveEvent = {
|
|
channel: channel,
|
|
type: type,
|
|
payload: {
|
|
data: data.payload,
|
|
ids: [data.payload.id],
|
|
},
|
|
date: date,
|
|
};
|
|
|
|
callback(liveEvent);
|
|
};
|
|
|
|
return () => {
|
|
ws.close();
|
|
};
|
|
}
|
|
|
|
const liveProvider = (apiUrl: string): LiveProvider => ({
|
|
subscribe: ({ channel, params, callback }) => {
|
|
const { resource, subscriptionType, id, ids } = params ?? {};
|
|
|
|
if (!subscriptionType) {
|
|
throw new Error("[useSubscription]: `subscriptionType` is required in `params`");
|
|
}
|
|
|
|
if (!resource) {
|
|
throw new Error("[useSubscription]: `resource` is required in `params`");
|
|
}
|
|
|
|
let idList: BaseKey[];
|
|
if (ids) idList = ids;
|
|
else if (id) idList = [id];
|
|
else {
|
|
// No ID specified, subscribe to all IDs
|
|
return [subscribeSingle(apiUrl, channel, resource, callback)];
|
|
}
|
|
|
|
return idList.map((id) => {
|
|
return subscribeSingle(apiUrl, channel, resource, callback, id);
|
|
});
|
|
},
|
|
unsubscribe: (closers: (() => void)[]) => {
|
|
closers.forEach((fn) => fn());
|
|
},
|
|
});
|
|
|
|
export default liveProvider;
|