mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 05:03:33 +00:00
Finalize Foundry sync flow
This commit is contained in:
parent
5c70cbcd23
commit
1c852cc5f8
14 changed files with 768 additions and 187 deletions
|
|
@ -156,6 +156,7 @@ export interface BackendMetadata {
|
|||
|
||||
export interface BackendClient {
|
||||
getAppSnapshot(): Promise<FoundryAppSnapshot>;
|
||||
subscribeApp(listener: () => void): () => void;
|
||||
signInWithGithub(): Promise<void>;
|
||||
signOutApp(): Promise<FoundryAppSnapshot>;
|
||||
skipAppStarterRepo(): Promise<FoundryAppSnapshot>;
|
||||
|
|
@ -405,6 +406,10 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
disposeConnPromise: Promise<(() => Promise<void>) | null> | null;
|
||||
}
|
||||
>();
|
||||
const appSubscriptions = {
|
||||
listeners: new Set<() => void>(),
|
||||
disposeConnPromise: null as Promise<(() => Promise<void>) | null> | null,
|
||||
};
|
||||
const sandboxProcessSubscriptions = new Map<
|
||||
string,
|
||||
{
|
||||
|
|
@ -664,6 +669,66 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
};
|
||||
};
|
||||
|
||||
const subscribeApp = (listener: () => void): (() => void) => {
|
||||
appSubscriptions.listeners.add(listener);
|
||||
|
||||
const ensureConnection = () => {
|
||||
if (appSubscriptions.disposeConnPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reconnecting = false;
|
||||
let disposeConnPromise: Promise<(() => Promise<void>) | null> | null = null;
|
||||
disposeConnPromise = (async () => {
|
||||
const handle = await workspace("app");
|
||||
const conn = (handle as any).connect();
|
||||
const unsubscribeEvent = conn.on("appUpdated", () => {
|
||||
for (const currentListener of [...appSubscriptions.listeners]) {
|
||||
currentListener();
|
||||
}
|
||||
});
|
||||
const unsubscribeError = conn.onError(() => {
|
||||
if (reconnecting) {
|
||||
return;
|
||||
}
|
||||
reconnecting = true;
|
||||
if (appSubscriptions.disposeConnPromise !== disposeConnPromise) {
|
||||
return;
|
||||
}
|
||||
appSubscriptions.disposeConnPromise = null;
|
||||
void disposeConnPromise?.then(async (disposeConn) => {
|
||||
await disposeConn?.();
|
||||
});
|
||||
if (appSubscriptions.listeners.size > 0) {
|
||||
ensureConnection();
|
||||
for (const currentListener of [...appSubscriptions.listeners]) {
|
||||
currentListener();
|
||||
}
|
||||
}
|
||||
});
|
||||
return async () => {
|
||||
unsubscribeEvent();
|
||||
unsubscribeError();
|
||||
await conn.dispose();
|
||||
};
|
||||
})().catch(() => null);
|
||||
appSubscriptions.disposeConnPromise = disposeConnPromise;
|
||||
};
|
||||
|
||||
ensureConnection();
|
||||
|
||||
return () => {
|
||||
appSubscriptions.listeners.delete(listener);
|
||||
if (appSubscriptions.listeners.size > 0) {
|
||||
return;
|
||||
}
|
||||
void appSubscriptions.disposeConnPromise?.then(async (disposeConn) => {
|
||||
await disposeConn?.();
|
||||
});
|
||||
appSubscriptions.disposeConnPromise = null;
|
||||
};
|
||||
};
|
||||
|
||||
const sandboxProcessSubscriptionKey = (workspaceId: string, providerId: ProviderId, sandboxId: string): string => `${workspaceId}:${providerId}:${sandboxId}`;
|
||||
|
||||
const subscribeSandboxProcesses = (workspaceId: string, providerId: ProviderId, sandboxId: string, listener: () => void): (() => void) => {
|
||||
|
|
@ -723,6 +788,10 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
return await appRequest<FoundryAppSnapshot>("/app/snapshot");
|
||||
},
|
||||
|
||||
subscribeApp(listener: () => void): () => void {
|
||||
return subscribeApp(listener);
|
||||
},
|
||||
|
||||
async signInWithGithub(): Promise<void> {
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.assign(`${options.endpoint.replace(/\/$/, "")}/app/auth/github/start`);
|
||||
|
|
|
|||
|
|
@ -548,15 +548,11 @@ class MockFoundryAppStore implements MockFoundryAppClient {
|
|||
|
||||
async selectOrganization(organizationId: string): Promise<void> {
|
||||
await this.injectAsyncLatency();
|
||||
const org = this.requireOrganization(organizationId);
|
||||
this.requireOrganization(organizationId);
|
||||
this.updateSnapshot((current) => ({
|
||||
...current,
|
||||
activeOrganizationId: organizationId,
|
||||
}));
|
||||
|
||||
if (org.github.syncStatus !== "synced") {
|
||||
await this.triggerGithubSync(organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async updateOrganizationProfile(input: UpdateMockOrganizationProfileInput): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -225,6 +225,10 @@ export function createMockBackendClient(defaultWorkspaceId = "default"): Backend
|
|||
return unsupportedAppSnapshot();
|
||||
},
|
||||
|
||||
subscribeApp(): () => void {
|
||||
return () => {};
|
||||
},
|
||||
|
||||
async signInWithGithub(): Promise<void> {
|
||||
notSupported("signInWithGithub");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
};
|
||||
private readonly listeners = new Set<() => void>();
|
||||
private refreshPromise: Promise<void> | null = null;
|
||||
private syncPollTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private disposeBackendSubscription: (() => void) | null = null;
|
||||
|
||||
constructor(options: RemoteFoundryAppClientOptions) {
|
||||
this.backend = options.backend;
|
||||
|
|
@ -37,9 +37,18 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
|
||||
subscribe(listener: () => void): () => void {
|
||||
this.listeners.add(listener);
|
||||
if (!this.disposeBackendSubscription) {
|
||||
this.disposeBackendSubscription = this.backend.subscribeApp(() => {
|
||||
void this.refresh();
|
||||
});
|
||||
}
|
||||
void this.refresh();
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
if (this.listeners.size === 0 && this.disposeBackendSubscription) {
|
||||
this.disposeBackendSubscription();
|
||||
this.disposeBackendSubscription = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +75,6 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
async selectOrganization(organizationId: string): Promise<void> {
|
||||
this.snapshot = await this.backend.selectAppOrganization(organizationId);
|
||||
this.notify();
|
||||
this.scheduleSyncPollingIfNeeded();
|
||||
}
|
||||
|
||||
async updateOrganizationProfile(input: UpdateFoundryOrganizationProfileInput): Promise<void> {
|
||||
|
|
@ -77,7 +85,6 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
async triggerGithubSync(organizationId: string): Promise<void> {
|
||||
this.snapshot = await this.backend.triggerAppRepoImport(organizationId);
|
||||
this.notify();
|
||||
this.scheduleSyncPollingIfNeeded();
|
||||
}
|
||||
|
||||
async clearOrganizationRuntimeIssues(organizationId: string, actorId?: string): Promise<void> {
|
||||
|
|
@ -112,22 +119,6 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
this.notify();
|
||||
}
|
||||
|
||||
private scheduleSyncPollingIfNeeded(): void {
|
||||
if (this.syncPollTimeout) {
|
||||
clearTimeout(this.syncPollTimeout);
|
||||
this.syncPollTimeout = null;
|
||||
}
|
||||
|
||||
if (!this.snapshot.organizations.some((organization) => organization.github.syncStatus === "syncing")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncPollTimeout = setTimeout(() => {
|
||||
this.syncPollTimeout = null;
|
||||
void this.refresh();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private async refresh(): Promise<void> {
|
||||
if (this.refreshPromise) {
|
||||
await this.refreshPromise;
|
||||
|
|
@ -137,7 +128,6 @@ class RemoteFoundryAppStore implements FoundryAppClient {
|
|||
this.refreshPromise = (async () => {
|
||||
this.snapshot = await this.backend.getAppSnapshot();
|
||||
this.notify();
|
||||
this.scheduleSyncPollingIfNeeded();
|
||||
})().finally(() => {
|
||||
this.refreshPromise = null;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue