mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
fix React cloud sign-in and workspace reload state
This commit is contained in:
@@ -88,7 +88,23 @@ export const desktopBridge: DesktopBridge = new Proxy({} as DesktopBridge, {
|
||||
|
||||
export const desktopFetch: typeof globalThis.fetch = (input, init) => {
|
||||
if (isElectronDesktopRuntime()) {
|
||||
return globalThis.fetch(input, init);
|
||||
return invokeElectronHelper<{
|
||||
status: number;
|
||||
statusText: string;
|
||||
headers: [string, string][];
|
||||
body: string;
|
||||
}>("__fetch", typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url, {
|
||||
method: init?.method,
|
||||
headers: init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : undefined,
|
||||
body: typeof init?.body === "string" ? init.body : undefined,
|
||||
}).then(
|
||||
(result) =>
|
||||
new Response(result.body, {
|
||||
status: result.status,
|
||||
statusText: result.statusText,
|
||||
headers: result.headers,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return tauriBridge.desktopFetch(input, init);
|
||||
};
|
||||
|
||||
@@ -16,9 +16,19 @@ import {
|
||||
DenApiError,
|
||||
ensureDenActiveOrganization,
|
||||
readDenSettings,
|
||||
writeDenSettings,
|
||||
type DenUser,
|
||||
} from "../../../app/lib/den";
|
||||
import { denSessionUpdatedEvent } from "../../../app/lib/den-session-events";
|
||||
import {
|
||||
denSessionUpdatedEvent,
|
||||
dispatchDenSessionUpdated,
|
||||
} from "../../../app/lib/den-session-events";
|
||||
import {
|
||||
deepLinkBridgeEvent,
|
||||
drainPendingDeepLinks,
|
||||
type DeepLinkBridgeDetail,
|
||||
} from "../../../app/lib/deep-link-bridge";
|
||||
import { parseDenAuthDeepLink } from "../../../app/lib/openwork-links";
|
||||
|
||||
export type DenAuthStatus = "checking" | "signed_in" | "signed_out";
|
||||
|
||||
@@ -48,6 +58,7 @@ export function DenAuthProvider({ children }: DenAuthProviderProps) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Monotonic token so stale async refreshes can't clobber a newer result.
|
||||
const refreshTokenRef = useRef(0);
|
||||
const handledGrantsRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const currentRun = ++refreshTokenRef.current;
|
||||
@@ -114,6 +125,60 @@ export function DenAuthProvider({ children }: DenAuthProviderProps) {
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const handleUrls = (urls: readonly string[]) => {
|
||||
for (const rawUrl of urls) {
|
||||
const parsed = parseDenAuthDeepLink(rawUrl);
|
||||
if (!parsed || handledGrantsRef.current.has(parsed.grant)) continue;
|
||||
handledGrantsRef.current.add(parsed.grant);
|
||||
|
||||
void createDenClient({ baseUrl: parsed.denBaseUrl })
|
||||
.exchangeDesktopHandoff(parsed.grant)
|
||||
.then((result) => {
|
||||
if (!result.token) {
|
||||
throw new Error("Failed to sign in to OpenWork Cloud.");
|
||||
}
|
||||
|
||||
writeDenSettings({
|
||||
baseUrl: parsed.denBaseUrl,
|
||||
authToken: result.token,
|
||||
activeOrgId: null,
|
||||
activeOrgSlug: null,
|
||||
activeOrgName: null,
|
||||
});
|
||||
|
||||
dispatchDenSessionUpdated({
|
||||
status: "success",
|
||||
baseUrl: parsed.denBaseUrl,
|
||||
token: result.token,
|
||||
user: result.user,
|
||||
email: result.user?.email ?? null,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
handledGrantsRef.current.delete(parsed.grant);
|
||||
dispatchDenSessionUpdated({
|
||||
status: "error",
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to sign in to OpenWork Cloud.",
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleUrls(drainPendingDeepLinks(window));
|
||||
const handleDeepLink = (event: Event) => {
|
||||
handleUrls(((event as CustomEvent<DeepLinkBridgeDetail>).detail?.urls ?? []) as string[]);
|
||||
};
|
||||
|
||||
window.addEventListener(deepLinkBridgeEvent, handleDeepLink);
|
||||
return () => window.removeEventListener(deepLinkBridgeEvent, handleDeepLink);
|
||||
}, []);
|
||||
|
||||
const value = useMemo<DenAuthStore>(
|
||||
() => ({
|
||||
status,
|
||||
|
||||
@@ -115,7 +115,7 @@ export function ReloadCoordinatorProvider({ children }: { children: ReactNode })
|
||||
<div className="pointer-events-none fixed right-4 top-4 z-50 w-[min(24rem,calc(100vw-1.5rem))] max-w-full sm:right-6 sm:top-6">
|
||||
<div className="pointer-events-auto">
|
||||
<ReloadWorkspaceToast
|
||||
open={systemState.reload.reloadPending}
|
||||
open={systemState.reload.reloadPending && activeSessions.length === 0}
|
||||
title={systemState.reloadCopy.title}
|
||||
description={systemState.reloadCopy.body}
|
||||
trigger={systemState.reload.reloadTrigger}
|
||||
|
||||
@@ -180,9 +180,9 @@ function mergeRouteWorkspaces(
|
||||
const merged = match
|
||||
? {
|
||||
...workspace,
|
||||
displayName: match.displayName?.trim()
|
||||
? match.displayName
|
||||
: workspace.displayName,
|
||||
displayName: workspace.displayName?.trim()
|
||||
? workspace.displayName
|
||||
: match.displayName,
|
||||
name: match.name?.trim() ? match.name : workspace.name,
|
||||
}
|
||||
: workspace;
|
||||
@@ -227,6 +227,14 @@ function toSessionGroups(
|
||||
}));
|
||||
}
|
||||
|
||||
function isActiveSessionStatus(status: unknown) {
|
||||
return status === "running" || status === "retry" || status === "busy";
|
||||
}
|
||||
|
||||
function getSessionStatus(session: any) {
|
||||
return session?.status ?? session?.state ?? session?.runStatus ?? null;
|
||||
}
|
||||
|
||||
async function fileToDataUrl(file: File) {
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -372,6 +380,21 @@ export function SessionRoute() {
|
||||
workspaceLabel,
|
||||
});
|
||||
|
||||
const activeReloadBlockingSessions = useMemo(
|
||||
() =>
|
||||
Object.values(sessionsByWorkspaceId)
|
||||
.flat()
|
||||
.filter((session) => isActiveSessionStatus(getSessionStatus(session)))
|
||||
.map((session: any) => ({
|
||||
id: String(session?.id ?? ""),
|
||||
title:
|
||||
String(session?.title ?? session?.slug ?? session?.id ?? "").trim() ||
|
||||
t("session.untitled"),
|
||||
}))
|
||||
.filter((session) => session.id.length > 0),
|
||||
[sessionsByWorkspaceId],
|
||||
);
|
||||
|
||||
const backgroundSessionLoadInFlight = useRef<Set<string>>(new Set());
|
||||
const loadWorkspaceSessionsInBackground = useCallback(
|
||||
async (openworkClient: OpenworkServerClient, workspaces: RouteWorkspace[]) => {
|
||||
@@ -575,9 +598,9 @@ export function SessionRoute() {
|
||||
return reloadCoordinator.registerWorkspaceReloadControls({
|
||||
canReloadWorkspaceEngine: () => Boolean(client && selectedWorkspaceId),
|
||||
reloadWorkspaceEngine: reloadWorkspaceEngineFromUi,
|
||||
activeSessions: () => [],
|
||||
activeSessions: () => activeReloadBlockingSessions,
|
||||
});
|
||||
}, [client, reloadCoordinator, reloadWorkspaceEngineFromUi, selectedWorkspaceId]);
|
||||
}, [activeReloadBlockingSessions, client, reloadCoordinator, reloadWorkspaceEngineFromUi, selectedWorkspaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!client || !selectedWorkspaceId) return;
|
||||
|
||||
@@ -109,9 +109,9 @@ function mergeRouteWorkspaces(
|
||||
const merged = match
|
||||
? {
|
||||
...workspace,
|
||||
displayName: match.displayName?.trim()
|
||||
? match.displayName
|
||||
: workspace.displayName,
|
||||
displayName: workspace.displayName?.trim()
|
||||
? workspace.displayName
|
||||
: match.displayName,
|
||||
name: match.name?.trim() ? match.name : workspace.name,
|
||||
}
|
||||
: workspace;
|
||||
|
||||
@@ -1140,6 +1140,22 @@ async function handleDesktopInvoke(event, command, ...args) {
|
||||
shell.showItemInFolder(target);
|
||||
return undefined;
|
||||
}
|
||||
case "__fetch": {
|
||||
const url = String(args[0] ?? "").trim();
|
||||
const init = args[1] ?? {};
|
||||
if (!url) throw new Error("URL is required.");
|
||||
const response = await fetch(url, {
|
||||
method: typeof init.method === "string" ? init.method : undefined,
|
||||
headers: init.headers && typeof init.headers === "object" ? init.headers : undefined,
|
||||
body: typeof init.body === "string" ? init.body : undefined,
|
||||
});
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: Array.from(response.headers.entries()),
|
||||
body: await response.text(),
|
||||
};
|
||||
}
|
||||
case "__homeDir":
|
||||
return os.homedir();
|
||||
case "__joinPath":
|
||||
|
||||
Reference in New Issue
Block a user