Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions apps/mobile/src/features/auth/hooks/useProjectsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { authedFetch, getBaseUrl } from "@/lib/api";
import { useAuthStore } from "../stores/authStore";

export interface ProjectSummary {
Expand All @@ -14,21 +15,19 @@ export interface ProjectSummary {
* rather than dropping the project from the list.
*/
export function useProjectsQuery() {
const { cloudRegion, oauthAccessToken, scopedTeams, getCloudUrlFromRegion } =
useAuthStore();
const { cloudRegion, oauthAccessToken, scopedTeams } = useAuthStore();

return useQuery({
queryKey: ["projects", cloudRegion, scopedTeams],
queryFn: async (): Promise<ProjectSummary[]> => {
if (!cloudRegion) throw new Error("No cloud region");
const baseUrl = getCloudUrlFromRegion(cloudRegion);
const baseUrl = getBaseUrl();

return Promise.all(
scopedTeams.map(async (id): Promise<ProjectSummary> => {
try {
const response = await fetch(`${baseUrl}/api/projects/${id}/`, {
headers: { Authorization: `Bearer ${oauthAccessToken}` },
});
const response = await authedFetch(
`${baseUrl}/api/projects/${id}/`,
);
if (!response.ok) return { id, name: `Project ${id}` };
const data: { name?: string } = await response.json();
return { id, name: data.name || `Project ${id}` };
Expand Down
12 changes: 3 additions & 9 deletions apps/mobile/src/features/auth/hooks/useUserQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { authedFetch, getBaseUrl } from "@/lib/api";
import { useAuthStore } from "../stores/authStore";

export interface UserData {
Expand All @@ -19,19 +20,12 @@ export interface UserData {
}

export function useUserQuery() {
const { cloudRegion, oauthAccessToken, getCloudUrlFromRegion } =
useAuthStore();
const { cloudRegion, oauthAccessToken } = useAuthStore();

return useQuery({
queryKey: ["user", "me"],
queryFn: async (): Promise<UserData> => {
if (!cloudRegion) throw new Error("No cloud region");
const baseUrl = getCloudUrlFromRegion(cloudRegion);
const response = await fetch(`${baseUrl}/api/users/@me/`, {
headers: {
Authorization: `Bearer ${oauthAccessToken}`,
},
});
const response = await authedFetch(`${getBaseUrl()}/api/users/@me/`);

if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.statusText}`);
Expand Down
33 changes: 9 additions & 24 deletions apps/mobile/src/features/inbox/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { fetch } from "expo/fetch";
import { HttpError } from "@/features/tasks/api";
import { getBaseUrl, getHeaders, getProjectId } from "@/lib/api";
import { authedFetch, getBaseUrl, getProjectId } from "@/lib/api";
import { logger } from "@/lib/logger";
import type { DismissalReasonOptionValue } from "./constants";

Expand All @@ -24,7 +23,6 @@ export async function getSignalReports(
): Promise<SignalReportsResponse> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const url = new URL(`${baseUrl}/api/projects/${projectId}/signals/reports/`);

Expand All @@ -47,7 +45,7 @@ export async function getSignalReports(
url.searchParams.set("suggested_reviewers", params.suggested_reviewers);
}

const response = await fetch(url.toString(), { headers });
const response = await authedFetch(url.toString());

if (!response.ok) {
throw new HttpError(
Expand All @@ -69,11 +67,9 @@ export async function getSignalReport(
): Promise<SignalReport | null> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/`,
{ headers },
);

if (response.status === 404 || response.status === 403) {
Expand All @@ -94,11 +90,9 @@ export async function getSignalReport(
export async function getSignalProcessingState(): Promise<SignalProcessingStateResponse> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/processing_state/`,
{ headers },
);

if (!response.ok) {
Expand All @@ -117,7 +111,6 @@ export async function getAvailableSuggestedReviewers(
): Promise<AvailableSuggestedReviewersResponse> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const url = new URL(
`${baseUrl}/api/projects/${projectId}/signals/reports/available_reviewers/`,
Expand All @@ -127,7 +120,7 @@ export async function getAvailableSuggestedReviewers(
url.searchParams.set("query", query.trim());
}

const response = await fetch(url.toString(), { headers });
const response = await authedFetch(url.toString());

if (!response.ok) {
throw new HttpError(
Expand Down Expand Up @@ -160,11 +153,9 @@ export async function getSignalReportTasks(
): Promise<SignalReportTask[]> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/tasks/`,
{ headers },
);

if (!response.ok) {
Expand All @@ -184,11 +175,9 @@ export async function getSignalReportArtefacts(
): Promise<SignalReportArtefactsResponse> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/artefacts/`,
{ headers },
);

if (!response.ok) {
Expand All @@ -211,11 +200,9 @@ export async function getSignalReportSignals(
): Promise<SignalReportSignalsResponse> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/signals/`,
{ headers },
);

if (!response.ok) {
Expand Down Expand Up @@ -268,13 +255,11 @@ export async function dismissSignalReport(
): Promise<SignalReport> {
const baseUrl = getBaseUrl();
const projectId = getProjectId();
const headers = getHeaders();

const response = await fetch(
const response = await authedFetch(
`${baseUrl}/api/projects/${projectId}/signals/reports/${reportId}/state/`,
{
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
state: "suppressed",
dismissal_reason: input.reason,
Expand Down
33 changes: 12 additions & 21 deletions apps/mobile/src/features/mcp/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fetch } from "expo/fetch";
import { getBaseUrl, getHeaders, getProjectId } from "@/lib/api";
import { authedFetch, getBaseUrl, getProjectId } from "@/lib/api";
import type {
InstallCustomMcpServerOptions,
InstallMcpTemplateOptions,
Expand Down Expand Up @@ -37,9 +36,8 @@ export async function getMcpRecommendedServers(): Promise<
> {
const base = getBaseUrl();
const projectId = getProjectId();
const response = await fetch(
const response = await authedFetch(
`${base}/api/environments/${projectId}/mcp_servers/`,
{ headers: getHeaders() },
);
const data = await readJsonOrThrow<
McpRecommendedServer[] | { results?: McpRecommendedServer[] }
Expand All @@ -51,7 +49,7 @@ export async function getMcpRecommendedServers(): Promise<
export async function getMcpServerInstallations(): Promise<
McpServerInstallation[]
> {
const response = await fetch(`${mcpBaseUrl()}/`, { headers: getHeaders() });
const response = await authedFetch(`${mcpBaseUrl()}/`);
const data = await readJsonOrThrow<
McpServerInstallation[] | { results?: McpServerInstallation[] }
>(response, "Failed to fetch MCP server installations");
Expand All @@ -62,9 +60,8 @@ export async function getMcpServerInstallations(): Promise<
export async function installCustomMcpServer(
options: InstallCustomMcpServerOptions,
): Promise<McpInstallResponse> {
const response = await fetch(`${mcpBaseUrl()}/install_custom/`, {
const response = await authedFetch(`${mcpBaseUrl()}/install_custom/`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify(options),
});
return readJsonOrThrow<McpInstallResponse>(
Expand All @@ -77,9 +74,8 @@ export async function installCustomMcpServer(
export async function installMcpTemplate(
options: InstallMcpTemplateOptions,
): Promise<McpInstallResponse> {
const response = await fetch(`${mcpBaseUrl()}/install_template/`, {
const response = await authedFetch(`${mcpBaseUrl()}/install_template/`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify(options),
});
return readJsonOrThrow<McpInstallResponse>(
Expand All @@ -93,9 +89,8 @@ export async function updateMcpServerInstallation(
installationId: string,
updates: UpdateMcpServerInstallationOptions,
): Promise<McpServerInstallation> {
const response = await fetch(`${mcpBaseUrl()}/${installationId}/`, {
const response = await authedFetch(`${mcpBaseUrl()}/${installationId}/`, {
method: "PATCH",
headers: getHeaders(),
body: JSON.stringify(updates),
});
return readJsonOrThrow<McpServerInstallation>(
Expand All @@ -108,9 +103,8 @@ export async function updateMcpServerInstallation(
export async function uninstallMcpServer(
installationId: string,
): Promise<void> {
const response = await fetch(`${mcpBaseUrl()}/${installationId}/`, {
const response = await authedFetch(`${mcpBaseUrl()}/${installationId}/`, {
method: "DELETE",
headers: getHeaders(),
});
if (!response.ok && response.status !== 204) {
throw new Error(`Failed to uninstall MCP server: ${response.statusText}`);
Expand All @@ -131,9 +125,8 @@ export async function authorizeMcpInstallation(options: {
if (options.posthog_code_callback_url) {
params.set("posthog_code_callback_url", options.posthog_code_callback_url);
}
const response = await fetch(
const response = await authedFetch(
`${mcpBaseUrl()}/authorize/?${params.toString()}`,
{ headers: getHeaders() },
);
return readJsonOrThrow<McpOAuthRedirectResponse>(
response,
Expand All @@ -149,9 +142,8 @@ export async function getMcpInstallationTools(
const params = new URLSearchParams();
if (options.includeRemoved) params.set("include_removed", "1");
const query = params.toString();
const response = await fetch(
const response = await authedFetch(
`${mcpBaseUrl()}/${installationId}/tools/${query ? `?${query}` : ""}`,
{ headers: getHeaders() },
);
const data = await readJsonOrThrow<
McpInstallationTool[] | { results?: McpInstallationTool[] }
Expand All @@ -165,11 +157,10 @@ export async function updateMcpToolApproval(
toolName: string,
approval_state: McpApprovalState,
): Promise<McpInstallationTool> {
const response = await fetch(
const response = await authedFetch(
`${mcpBaseUrl()}/${installationId}/tools/${encodeURIComponent(toolName)}/`,
{
method: "PATCH",
headers: getHeaders(),
body: JSON.stringify({ approval_state }),
},
);
Expand All @@ -183,9 +174,9 @@ export async function updateMcpToolApproval(
export async function refreshMcpInstallationTools(
installationId: string,
): Promise<McpInstallationTool[]> {
const response = await fetch(
const response = await authedFetch(
`${mcpBaseUrl()}/${installationId}/tools/refresh/`,
{ method: "POST", headers: getHeaders() },
{ method: "POST" },
);
const data = await readJsonOrThrow<
McpInstallationTool[] | { results?: McpInstallationTool[] }
Expand Down
13 changes: 9 additions & 4 deletions apps/mobile/src/features/tasks/api.automations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ vi.mock("expo/fetch", () => ({

vi.mock("@/lib/api", () => ({
getBaseUrl: () => "https://app.posthog.test",
getHeaders: () => ({
Authorization: "Bearer token",
"Content-Type": "application/json",
}),
getProjectId: () => 42,
authedFetch: (url: string, init?: RequestInit) =>
mockFetch(url, {
...init,
headers: {
Authorization: "Bearer token",
"Content-Type": "application/json",
...((init?.headers as Record<string, string> | undefined) ?? {}),
},
}),
}));

import {
Expand Down
Loading
Loading