fix(frontend): ws replacement
This commit is contained in:
109
frontend/src/lib/convex-polling.svelte.ts
Normal file
109
frontend/src/lib/convex-polling.svelte.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { ConvexHttpClient } from 'convex/browser';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import type { FunctionReference, FunctionArgs, FunctionReturnType } from 'convex/server';
|
||||
|
||||
const POLLING_CONTEXT_KEY = 'convex-polling';
|
||||
const POLL_INTERVAL = 1000;
|
||||
|
||||
type PollingContext = {
|
||||
client: ConvexHttpClient;
|
||||
};
|
||||
|
||||
export function hasWebSocketSupport(): boolean {
|
||||
if (typeof window === 'undefined') return true;
|
||||
try {
|
||||
return 'WebSocket' in window && typeof WebSocket !== 'undefined';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function setupPollingConvex(url: string): void {
|
||||
const client = new ConvexHttpClient(url);
|
||||
setContext<PollingContext>(POLLING_CONTEXT_KEY, { client });
|
||||
}
|
||||
|
||||
export function usePollingClient(): ConvexHttpClient {
|
||||
const ctx = getContext<PollingContext>(POLLING_CONTEXT_KEY);
|
||||
if (!ctx) {
|
||||
throw new Error('Convex polling client not set up. Call setupPollingConvex first.');
|
||||
}
|
||||
return ctx.client;
|
||||
}
|
||||
|
||||
type QueryState<T> = {
|
||||
data: T | undefined;
|
||||
error: Error | null;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
export function usePollingMutation<Mutation extends FunctionReference<'mutation'>>(
|
||||
mutation: Mutation
|
||||
): (args: FunctionArgs<Mutation>) => Promise<FunctionReturnType<Mutation>> {
|
||||
const client = usePollingClient();
|
||||
return (args: FunctionArgs<Mutation>) => client.mutation(mutation, args);
|
||||
}
|
||||
|
||||
export function usePollingQuery<Query extends FunctionReference<'query'>>(
|
||||
query: Query,
|
||||
argsGetter: () => FunctionArgs<Query> | 'skip'
|
||||
): { data: FunctionReturnType<Query> | undefined; error: Error | null; isLoading: boolean } {
|
||||
const client = usePollingClient();
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let state = $state<QueryState<FunctionReturnType<Query>>>({
|
||||
data: undefined,
|
||||
error: null,
|
||||
isLoading: true
|
||||
});
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
let lastArgsJson = '';
|
||||
|
||||
async function poll() {
|
||||
const args = argsGetter();
|
||||
if (args === 'skip') {
|
||||
state.isLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const argsJson = JSON.stringify(args);
|
||||
if (argsJson !== lastArgsJson) {
|
||||
state.isLoading = true;
|
||||
lastArgsJson = argsJson;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await client.query(query, args);
|
||||
state.data = result;
|
||||
state.error = null;
|
||||
state.isLoading = false;
|
||||
} catch (err) {
|
||||
state.error = err instanceof Error ? err : new Error(String(err));
|
||||
state.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
poll();
|
||||
intervalId = setInterval(poll, POLL_INTERVAL);
|
||||
|
||||
return () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
get data() {
|
||||
return state.data;
|
||||
},
|
||||
get error() {
|
||||
return state.error;
|
||||
},
|
||||
get isLoading() {
|
||||
return state.isLoading;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user