Preserve Zerodha callback state in frontend

This commit is contained in:
Thigazhezhilan J 2026-04-12 20:17:48 +05:30
parent ccaa363d48
commit bc1ea376a4
4 changed files with 88 additions and 22 deletions

View File

@ -7,7 +7,7 @@
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "npm run typecheck && tsx --test src/pages/admin/api.test.ts", "test": "npm run typecheck && tsx --test src/pages/admin/api.test.ts src/pages/zerodhaCallbackUtils.test.ts",
"preview": "vite preview", "preview": "vite preview",
"start": "vite --host 0.0.0.0 --port 3001" "start": "vite --host 0.0.0.0 --port 3001"
}, },

View File

@ -3,28 +3,16 @@ import { Loader2 } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { apiRequest } from "@/lib/queryClient"; import { apiRequest } from "@/lib/queryClient";
import PageEnter from "@/components/ui/page-enter"; import PageEnter from "@/components/ui/page-enter";
import {
buildZerodhaBackendCallbackUrl,
parseZerodhaCallbackParams,
type ZerodhaCallbackParams,
} from "./zerodhaCallbackUtils";
const CALLBACK_STORAGE_KEY = "zerodha:callback"; const CALLBACK_STORAGE_KEY = "zerodha:callback";
const RETURN_PATH = "/portfolio"; const RETURN_PATH = "/portfolio";
type CallbackParams = { const parseParams = (): ZerodhaCallbackParams => parseZerodhaCallbackParams(window.location.search);
flow: string;
status: string;
requestToken: string;
error: string;
errorDescription: string;
};
const parseParams = (): CallbackParams => {
const params = new URLSearchParams(window.location.search);
return {
flow: (params.get("flow") || "").trim(),
status: (params.get("status") || "").trim(),
requestToken: (params.get("request_token") || "").trim(),
error: (params.get("error") || params.get("error_type") || "").trim(),
errorDescription: (params.get("error_description") || "").trim(),
};
};
export default function ZerodhaCallback() { export default function ZerodhaCallback() {
useEffect(() => { useEffect(() => {
@ -50,9 +38,12 @@ export default function ZerodhaCallback() {
return; return;
} }
const callbackPath = if (!params.state) {
params.flow === "reconnect" ? "/broker/callback" : "/broker/zerodha/callback"; finalize("error", "Missing callback state in the redirect URL.");
const url = `${callbackPath}?request_token=${encodeURIComponent(params.requestToken)}`; return;
}
const url = buildZerodhaBackendCallbackUrl(params);
apiRequest("GET", url) apiRequest("GET", url)
.then(() => finalize("success")) .then(() => finalize("success"))
.catch((err: any) => finalize("error", err?.message || "Unable to complete the login.")); .catch((err: any) => finalize("error", err?.message || "Unable to complete the login."));

View File

@ -0,0 +1,43 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
buildZerodhaBackendCallbackUrl,
parseZerodhaCallbackParams,
} from "./zerodhaCallbackUtils";
test("parseZerodhaCallbackParams reads state from callback query", () => {
const parsed = parseZerodhaCallbackParams(
"?request_token=req-123&state=state-456&status=success&flow=reconnect",
);
assert.equal(parsed.requestToken, "req-123");
assert.equal(parsed.state, "state-456");
assert.equal(parsed.status, "success");
assert.equal(parsed.flow, "reconnect");
});
test("buildZerodhaBackendCallbackUrl forwards request token and state", () => {
const url = buildZerodhaBackendCallbackUrl({
flow: "connect",
status: "success",
requestToken: "req-123",
state: "state-456",
error: "",
errorDescription: "",
});
assert.equal(url, "/broker/zerodha/callback?request_token=req-123&state=state-456");
});
test("buildZerodhaBackendCallbackUrl uses reconnect callback route", () => {
const url = buildZerodhaBackendCallbackUrl({
flow: "reconnect",
status: "success",
requestToken: "req-123",
state: "state-456",
error: "",
errorDescription: "",
});
assert.equal(url, "/broker/callback?request_token=req-123&state=state-456");
});

View File

@ -0,0 +1,32 @@
export type ZerodhaCallbackParams = {
flow: string;
status: string;
requestToken: string;
state: string;
error: string;
errorDescription: string;
};
export function parseZerodhaCallbackParams(search: string): ZerodhaCallbackParams {
const params = new URLSearchParams(search);
return {
flow: (params.get("flow") || "").trim(),
status: (params.get("status") || "").trim(),
requestToken: (params.get("request_token") || "").trim(),
state: (params.get("state") || "").trim(),
error: (params.get("error") || params.get("error_type") || "").trim(),
errorDescription: (params.get("error_description") || "").trim(),
};
}
export function buildZerodhaBackendCallbackUrl(params: ZerodhaCallbackParams): string {
const callbackPath = params.flow === "reconnect" ? "/broker/callback" : "/broker/zerodha/callback";
const search = new URLSearchParams();
if (params.requestToken) {
search.set("request_token", params.requestToken);
}
if (params.state) {
search.set("state", params.state);
}
return `${callbackPath}?${search.toString()}`;
}