mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
packages
This commit is contained in:
89
packages/devtools-shared/src/context.tsx
Normal file
89
packages/devtools-shared/src/context.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from "react";
|
||||
import { DevtoolsEvent } from "./event-types";
|
||||
import { send } from "./send";
|
||||
import { receive } from "./receive";
|
||||
|
||||
type DevtoolsContextValue = {
|
||||
__devtools: boolean;
|
||||
httpUrl: string;
|
||||
wsUrl: string;
|
||||
ws: WebSocket | null;
|
||||
};
|
||||
|
||||
export const DevToolsContext = React.createContext<DevtoolsContextValue>({
|
||||
__devtools: false,
|
||||
httpUrl: "http://localhost:5001",
|
||||
wsUrl: "ws://localhost:5001",
|
||||
ws: null,
|
||||
});
|
||||
|
||||
type Props = React.PropsWithChildren<{
|
||||
__devtools?: boolean;
|
||||
url?: string | [httpUrl: string, wsUrl: string];
|
||||
}>;
|
||||
|
||||
export const DevToolsContextProvider: React.FC<Props> = ({
|
||||
__devtools,
|
||||
url = ["http://localhost:5001", "ws://localhost:5001"],
|
||||
children,
|
||||
}) => {
|
||||
const httpUrl = Array.isArray(url) ? url[0] : url;
|
||||
const wsUrl = Array.isArray(url)
|
||||
? url[1]
|
||||
: url.replace(/http(s)?:\/\//, "ws$1://");
|
||||
|
||||
const [values, setValues] = React.useState<DevtoolsContextValue>({
|
||||
__devtools: __devtools ?? false,
|
||||
httpUrl,
|
||||
wsUrl,
|
||||
ws: null,
|
||||
});
|
||||
|
||||
const [ws, setWs] = React.useState<WebSocket | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
const wsInstance = new WebSocket(values.wsUrl);
|
||||
|
||||
wsInstance.addEventListener("open", () => {
|
||||
if (!values.__devtools) {
|
||||
timeout = setTimeout(() => {
|
||||
send(wsInstance, DevtoolsEvent.DEVTOOLS_INIT, {
|
||||
url: window.location.origin,
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
setWs(wsInstance);
|
||||
|
||||
return () => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
|
||||
// In strict mode, the WebSocket instance might not be connected yet
|
||||
// so we need to wait for it to connect before closing it
|
||||
// otherwise it will log an unnecessary error in the console
|
||||
if (wsInstance.readyState === WebSocket.CONNECTING) {
|
||||
wsInstance.addEventListener("open", () => {
|
||||
wsInstance.close(1000, window.location.origin);
|
||||
});
|
||||
} else {
|
||||
wsInstance.close(1000, window.location.origin);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const contextValues = React.useMemo<DevtoolsContextValue>(
|
||||
() => ({
|
||||
...values,
|
||||
ws,
|
||||
}),
|
||||
[values, ws],
|
||||
);
|
||||
|
||||
return (
|
||||
<DevToolsContext.Provider value={contextValues}>
|
||||
{children}
|
||||
</DevToolsContext.Provider>
|
||||
);
|
||||
};
|
||||
85
packages/devtools-shared/src/event-types.ts
Normal file
85
packages/devtools-shared/src/event-types.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type {
|
||||
Mutation,
|
||||
MutationKey,
|
||||
MutationStatus,
|
||||
QueryKey,
|
||||
QueryState,
|
||||
QueryStatus,
|
||||
} from "@tanstack/react-query";
|
||||
import type { TraceType } from "./trace";
|
||||
|
||||
export enum DevtoolsEvent {
|
||||
RELOAD = "devtools:reload",
|
||||
DEVTOOLS_INIT = "devtools:init",
|
||||
DEVTOOLS_ALREADY_CONNECTED = "devtools:already-connected",
|
||||
ACTIVITY = "devtools:send-activity",
|
||||
DEVTOOLS_ACTIVITY_UPDATE = "devtools:activity-update",
|
||||
DEVTOOLS_CONNECTED_APP = "devtools:connected-app",
|
||||
DEVTOOLS_DISCONNECTED_APP = "devtools:disconnected-app",
|
||||
DEVTOOLS_HIGHLIGHT_IN_MONITOR = "devtools:highlight-in-monitor",
|
||||
DEVTOOLS_HIGHLIGHT_IN_MONITOR_ACTION = "devtools:highlight-in-monitor-action",
|
||||
DEVTOOLS_LOGIN_SUCCESS = "devtools:login-success",
|
||||
DEVTOOLS_DISPLAY_LOGIN_FAILURE = "devtools:display-login-failure",
|
||||
DEVTOOLS_LOGIN_FAILURE = "devtools:login-failure",
|
||||
DEVTOOLS_RELOAD_AFTER_LOGIN = "devtools:reload-after-login",
|
||||
DEVTOOLS_INVALIDATE_QUERY = "devtools:invalidate-query",
|
||||
DEVTOOLS_INVALIDATE_QUERY_ACTION = "devtools:invalidate-query-action",
|
||||
}
|
||||
|
||||
type Timestamps = {
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
type ActivityPayload =
|
||||
| {
|
||||
type: "mutation";
|
||||
identifier: string;
|
||||
key?: MutationKey;
|
||||
status?: MutationStatus;
|
||||
trace?: TraceType[];
|
||||
state: Mutation<any, any, any, any>["state"];
|
||||
variables?: Mutation<any, any, any, any>["state"]["variables"];
|
||||
hookName: string;
|
||||
resourcePath: string | null;
|
||||
resourceName?: string;
|
||||
legacyKey: boolean;
|
||||
}
|
||||
| {
|
||||
type: "query";
|
||||
identifier: string;
|
||||
key?: QueryKey;
|
||||
status?: QueryStatus;
|
||||
trace?: TraceType[];
|
||||
state: QueryState<any, any>;
|
||||
hookName: string;
|
||||
resourcePath: string | null;
|
||||
resourceName?: string;
|
||||
legacyKey: boolean;
|
||||
};
|
||||
|
||||
export type DevtoolsEventPayloads = {
|
||||
[DevtoolsEvent.RELOAD]: {};
|
||||
[DevtoolsEvent.DEVTOOLS_INIT]: { url: string };
|
||||
[DevtoolsEvent.DEVTOOLS_ALREADY_CONNECTED]: { url: string };
|
||||
[DevtoolsEvent.ACTIVITY]: ActivityPayload;
|
||||
[DevtoolsEvent.DEVTOOLS_ACTIVITY_UPDATE]: {
|
||||
updatedActivities: (ActivityPayload & Timestamps)[];
|
||||
};
|
||||
[DevtoolsEvent.DEVTOOLS_CONNECTED_APP]: { url: string | null };
|
||||
[DevtoolsEvent.DEVTOOLS_DISCONNECTED_APP]: {};
|
||||
[DevtoolsEvent.DEVTOOLS_HIGHLIGHT_IN_MONITOR]: { name: string };
|
||||
[DevtoolsEvent.DEVTOOLS_HIGHLIGHT_IN_MONITOR_ACTION]: { name: string };
|
||||
[DevtoolsEvent.DEVTOOLS_LOGIN_SUCCESS]: {};
|
||||
[DevtoolsEvent.DEVTOOLS_LOGIN_FAILURE]: {
|
||||
error: string | null;
|
||||
code: string | null;
|
||||
};
|
||||
[DevtoolsEvent.DEVTOOLS_DISPLAY_LOGIN_FAILURE]: {
|
||||
error: string | null;
|
||||
code: string | null;
|
||||
};
|
||||
[DevtoolsEvent.DEVTOOLS_RELOAD_AFTER_LOGIN]: {};
|
||||
[DevtoolsEvent.DEVTOOLS_INVALIDATE_QUERY]: { queryKey: QueryKey };
|
||||
[DevtoolsEvent.DEVTOOLS_INVALIDATE_QUERY_ACTION]: { queryKey: QueryKey };
|
||||
};
|
||||
12
packages/devtools-shared/src/feed.ts
Normal file
12
packages/devtools-shared/src/feed.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type FeedSection = {
|
||||
content: string;
|
||||
// frontmatter
|
||||
title: string;
|
||||
featured?: boolean;
|
||||
date: string;
|
||||
author: string;
|
||||
avatar: string;
|
||||
cover?: string;
|
||||
};
|
||||
|
||||
export type Feed = FeedSection[];
|
||||
14
packages/devtools-shared/src/index.ts
Normal file
14
packages/devtools-shared/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export { DevtoolsEvent, DevtoolsEventPayloads } from "./event-types.js";
|
||||
export { TraceType } from "./trace.js";
|
||||
export { Feed, FeedSection } from "./feed.js";
|
||||
export {
|
||||
PackageType,
|
||||
PackageLatestVersionType,
|
||||
AvailablePackageType,
|
||||
} from "./package.js";
|
||||
export { RefineHook, Scopes, hooksByScope, scopes } from "./scopes.js";
|
||||
|
||||
export { DevToolsContextProvider, DevToolsContext } from "./context.js";
|
||||
|
||||
export { send } from "./send.js";
|
||||
export { receive } from "./receive.js";
|
||||
19
packages/devtools-shared/src/package.ts
Normal file
19
packages/devtools-shared/src/package.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type PackageType = {
|
||||
name: string;
|
||||
currentVersion: string;
|
||||
description?: string;
|
||||
changelog?: string;
|
||||
documentation?: string;
|
||||
};
|
||||
|
||||
export type PackageLatestVersionType = {
|
||||
name: string;
|
||||
latestVersion: string;
|
||||
};
|
||||
|
||||
export type AvailablePackageType = {
|
||||
name: string;
|
||||
description: string;
|
||||
install: string;
|
||||
usage: string;
|
||||
};
|
||||
22
packages/devtools-shared/src/receive.ts
Normal file
22
packages/devtools-shared/src/receive.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// receive ws message by adding a listener to the ws object
|
||||
|
||||
import type { DevtoolsEvent, DevtoolsEventPayloads } from "./event-types";
|
||||
|
||||
export function receive<T extends DevtoolsEvent>(
|
||||
ws: WebSocket,
|
||||
event: T,
|
||||
callback: (payload: DevtoolsEventPayloads[T]) => void,
|
||||
) {
|
||||
const listener = (e: MessageEvent) => {
|
||||
const { event: receivedEvent, payload } = JSON.parse(e.data);
|
||||
if (event === receivedEvent) {
|
||||
callback(payload);
|
||||
}
|
||||
};
|
||||
|
||||
ws.addEventListener("message", listener);
|
||||
|
||||
return () => {
|
||||
ws.removeEventListener("message", listener);
|
||||
};
|
||||
}
|
||||
67
packages/devtools-shared/src/scopes.ts
Normal file
67
packages/devtools-shared/src/scopes.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export type RefineHook =
|
||||
| "useCan"
|
||||
| "useLog"
|
||||
| "useLogList"
|
||||
| "useCreate"
|
||||
| "useCreateMany"
|
||||
| "useCustom"
|
||||
| "useCustomMutation"
|
||||
| "useDelete"
|
||||
| "useDeleteMany"
|
||||
| "useInfiniteList"
|
||||
| "useList"
|
||||
| "useMany"
|
||||
| "useOne"
|
||||
| "useUpdate"
|
||||
| "useUpdateMany"
|
||||
| "useForgotPassword"
|
||||
| "useGetIdentity"
|
||||
| "useIsAuthenticated"
|
||||
| "useLogin"
|
||||
| "useLogout"
|
||||
| "useOnError"
|
||||
| "usePermissions"
|
||||
| "useRegister"
|
||||
| "useUpdatePassword";
|
||||
|
||||
export type Scopes = "data" | "audit-log" | "access-control" | "auth";
|
||||
|
||||
export const scopes: Record<RefineHook, Scopes> = {
|
||||
useCan: "access-control",
|
||||
useLog: "audit-log",
|
||||
useLogList: "audit-log",
|
||||
useCreate: "data",
|
||||
useCreateMany: "data",
|
||||
useCustom: "data",
|
||||
useCustomMutation: "data",
|
||||
useDelete: "data",
|
||||
useDeleteMany: "data",
|
||||
useInfiniteList: "data",
|
||||
useList: "data",
|
||||
useMany: "data",
|
||||
useOne: "data",
|
||||
useUpdate: "data",
|
||||
useUpdateMany: "data",
|
||||
useForgotPassword: "auth",
|
||||
useGetIdentity: "auth",
|
||||
useIsAuthenticated: "auth",
|
||||
useLogin: "auth",
|
||||
useLogout: "auth",
|
||||
useOnError: "auth",
|
||||
usePermissions: "auth",
|
||||
useRegister: "auth",
|
||||
useUpdatePassword: "auth",
|
||||
};
|
||||
|
||||
export const hooksByScope = Object.entries(scopes).reduce(
|
||||
(acc, [hook, scope]) => {
|
||||
if (!acc[scope]) {
|
||||
acc[scope] = [];
|
||||
}
|
||||
|
||||
acc[scope].push(hook as RefineHook);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<Scopes, RefineHook[]>,
|
||||
);
|
||||
24
packages/devtools-shared/src/send.ts
Normal file
24
packages/devtools-shared/src/send.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { DevtoolsEvent, DevtoolsEventPayloads } from "./event-types";
|
||||
|
||||
export async function send<T extends DevtoolsEvent>(
|
||||
ws: WebSocket,
|
||||
event: T,
|
||||
payload: DevtoolsEventPayloads[T],
|
||||
) {
|
||||
// check if the socket is open
|
||||
// if not, wait for it to open
|
||||
if (ws.readyState !== ws.OPEN) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const listener = () => {
|
||||
ws.send(JSON.stringify({ event, payload }));
|
||||
resolve();
|
||||
ws.removeEventListener("open", listener);
|
||||
};
|
||||
|
||||
ws.addEventListener("open", listener);
|
||||
});
|
||||
return;
|
||||
}
|
||||
ws.send(JSON.stringify({ event, payload }));
|
||||
return;
|
||||
}
|
||||
8
packages/devtools-shared/src/trace.ts
Normal file
8
packages/devtools-shared/src/trace.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type TraceType = {
|
||||
file?: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
function?: string;
|
||||
isRefine: boolean;
|
||||
packageName?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user