remove ofetch, replace with fetch

This commit is contained in:
Jorrin
2024-04-19 20:41:21 +02:00
parent eea4eab60b
commit 75f5256b20
15 changed files with 132 additions and 111 deletions

View File

@@ -2,29 +2,18 @@ import { Stack, useLocalSearchParams } from "expo-router";
import { useQuery } from "@tanstack/react-query";
import { H4, Paragraph, Text, View } from "tamagui";
import { getBackendMeta } from "@movie-web/api";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { MWButton } from "~/components/ui/Button";
import { MWCard } from "~/components/ui/Card";
// TODO: extract to function with cleanup and types
const getBackendMeta = (
url: string,
): Promise<{
description: string;
hasCaptcha: boolean;
name: string;
url: string;
}> => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return fetch(`${url}/meta`).then((res) => res.json());
};
export default function Page() {
const { url } = useLocalSearchParams();
const { url } = useLocalSearchParams<{ url: string }>();
const meta = useQuery({
queryKey: ["backendMeta", url],
queryFn: () => getBackendMeta(url as string),
queryFn: () => getBackendMeta(url),
});
return (
@@ -102,7 +91,7 @@ export default function Page() {
gap="$4"
>
<MWButton type="purple">I trust this server</MWButton>
<MWButton type="secondary">Go back</MWButton>
<MWButton type="cancel">Go back</MWButton>
<Paragraph color="$ash50" textAlign="center" fontWeight="$semibold">
Already have an account?{" "}

View File

@@ -4,35 +4,35 @@ export const MWButton = styled(Button, {
variants: {
type: {
primary: {
backgroundColor: "$buttonPrimaryBackground",
color: "$buttonPrimaryText",
backgroundColor: "white",
color: "black",
fontWeight: "bold",
pressStyle: {
backgroundColor: "$buttonPrimaryBackgroundHover",
backgroundColor: "$silver100",
},
},
secondary: {
backgroundColor: "$buttonSecondaryBackground",
color: "$buttonSecondaryText",
backgroundColor: "$ash700",
color: "$silver300",
fontWeight: "bold",
pressStyle: {
backgroundColor: "$buttonSecondaryBackgroundHover",
backgroundColor: "$ash500",
},
},
purple: {
backgroundColor: "$buttonPurpleBackground",
backgroundColor: "$purple500",
color: "white",
fontWeight: "bold",
pressStyle: {
backgroundColor: "$buttonPurpleBackgroundHover",
backgroundColor: "$purple400",
},
},
cancel: {
backgroundColor: "$buttonCancelBackground",
backgroundColor: "$ash500",
color: "white",
fontWeight: "bold",
pressStyle: {
backgroundColor: "$buttonCancelBackgroundHover",
backgroundColor: "$ash300",
},
},
},

View File

@@ -32,7 +32,6 @@
"dependencies": {
"@noble/hashes": "^1.4.0",
"@scure/bip39": "^1.3.0",
"node-forge": "^1.3.1",
"ofetch": "^1.3.4"
"node-forge": "^1.3.1"
}
}

View File

@@ -1,6 +1,5 @@
import { ofetch } from "ofetch";
import type { LoginResponse } from "./types";
import { f } from "./fetch";
export function getAuthHeaders(token: string): Record<string, string> {
return {
@@ -13,12 +12,12 @@ export async function accountLogin(
id: string,
deviceName: string,
): Promise<LoginResponse> {
return ofetch<LoginResponse>("/auth/login", {
return f<LoginResponse>("/auth/login", {
method: "POST",
body: {
id,
device: deviceName,
},
baseURL: url,
baseUrl: url,
});
}

View File

@@ -1,5 +1,3 @@
import { ofetch } from "ofetch";
import type {
AccountWithToken,
BookmarkInput,
@@ -7,6 +5,7 @@ import type {
BookmarkResponse,
} from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export function bookmarkMediaToInput(
tmdbId: string,
@@ -28,12 +27,12 @@ export async function addBookmark(
account: AccountWithToken,
input: BookmarkInput,
) {
return ofetch<BookmarkResponse>(
return f<BookmarkResponse>(
`/users/${account.userId}/bookmarks/${input.tmdbId}`,
{
method: "POST",
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
body: input,
},
);
@@ -44,12 +43,9 @@ export async function removeBookmark(
account: AccountWithToken,
id: string,
) {
return ofetch<{ tmdbId: string }>(
`/users/${account.userId}/bookmarks/${id}`,
{
method: "DELETE",
headers: getAuthHeaders(account.token),
baseURL: url,
},
);
return f<{ tmdbId: string }>(`/users/${account.userId}/bookmarks/${id}`, {
method: "DELETE",
headers: getAuthHeaders(account.token),
baseUrl: url,
});
}

View File

@@ -39,11 +39,7 @@ export function genMnemonic(): string {
return generateMnemonic(wordlist);
}
// eslint-disable-next-line @typescript-eslint/require-await
export async function signCode(
code: string,
privateKey: Uint8Array,
): Promise<Uint8Array> {
export function signCode(code: string, privateKey: Uint8Array): Uint8Array {
return forge.pki.ed25519.sign({
encoding: "utf8",
message: code,
@@ -62,8 +58,8 @@ export function bytesToBase64Url(bytes: Uint8Array): string {
.replace(/=+$/, "");
}
export async function signChallenge(keys: Keys, challengeCode: string) {
const signature = await signCode(challengeCode, keys.privateKey);
export function signChallenge(keys: Keys, challengeCode: string) {
const signature = signCode(challengeCode, keys.privateKey);
return bytesToBase64Url(signature);
}

53
packages/api/src/fetch.ts Normal file
View File

@@ -0,0 +1,53 @@
export interface FetcherOptions {
baseUrl?: string;
headers?: Record<string, string>;
query?: Record<string, string>;
method?: "HEAD" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
readHeaders?: string[];
body?: Record<string, any>;
}
export type FullUrlOptions = Pick<FetcherOptions, "query" | "baseUrl">;
export function makeFullUrl(url: string, ops?: FullUrlOptions): string {
// glue baseUrl and rest of url together
let leftSide = ops?.baseUrl ?? "";
let rightSide = url;
// left side should always end with slash, if its set
if (leftSide.length > 0 && !leftSide.endsWith("/")) leftSide += "/";
// right side should never start with slash
if (rightSide.startsWith("/")) rightSide = rightSide.slice(1);
const fullUrl = leftSide + rightSide;
if (!fullUrl.startsWith("http://") && !fullUrl.startsWith("https://"))
throw new Error(
`Invald URL -- URL doesn't start with a http scheme: '${fullUrl}'`,
);
const parsedUrl = new URL(fullUrl);
Object.entries(ops?.query ?? {}).forEach(([k, v]) => {
parsedUrl.searchParams.set(k, v);
});
return parsedUrl.toString();
}
export async function f<T>(url: string, ops?: FetcherOptions): Promise<T> {
const fullUrl = makeFullUrl(url, ops);
const response = await fetch(fullUrl, {
method: ops?.method ?? "GET",
headers: ops?.headers,
body: ops?.body ? JSON.stringify(ops.body) : undefined,
});
if (!response.ok) {
throw new Error(
`Failed to fetch '${fullUrl}' -- ${response.status} ${response.statusText}`,
);
}
const data = (await response.json()) as T;
return data;
}

View File

@@ -1,17 +1,16 @@
import { ofetch } from "ofetch";
import type { AccountWithToken, BookmarkInput, ProgressInput } from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export function importProgress(
url: string,
account: AccountWithToken,
progressItems: ProgressInput[],
) {
return ofetch<void>(`/users/${account.userId}/progress/import`, {
return f<void>(`/users/${account.userId}/progress/import`, {
method: "PUT",
body: progressItems,
baseURL: url,
baseUrl: url,
headers: getAuthHeaders(account.token),
});
}
@@ -21,10 +20,10 @@ export function importBookmarks(
account: AccountWithToken,
bookmarks: BookmarkInput[],
) {
return ofetch<void>(`/users/${account.userId}/bookmarks`, {
return f<void>(`/users/${account.userId}/bookmarks`, {
method: "PUT",
body: bookmarks,
baseURL: url,
baseUrl: url,
headers: getAuthHeaders(account.token),
});
}

View File

@@ -1,21 +1,20 @@
import { ofetch } from "ofetch";
import type {
ChallengeTokenResponse,
LoginInput,
LoginResponse,
} from "./types";
import { f } from "./fetch";
export async function getLoginChallengeToken(
url: string,
publicKey: string,
): Promise<ChallengeTokenResponse> {
return ofetch<ChallengeTokenResponse>("/auth/login/start", {
return f<ChallengeTokenResponse>("/auth/login/start", {
method: "POST",
body: {
publicKey,
},
baseURL: url,
baseUrl: url,
});
}
@@ -23,12 +22,12 @@ export async function loginAccount(
url: string,
data: LoginInput,
): Promise<LoginResponse> {
return ofetch<LoginResponse>("/auth/login/complete", {
return f<LoginResponse>("/auth/login/complete", {
method: "POST",
body: {
namespace: "movie-web",
...data,
},
baseURL: url,
baseUrl: url,
});
}

View File

@@ -1,9 +1,8 @@
import { ofetch } from "ofetch";
import type { MetaResponse } from "./types";
import { f } from "./fetch";
export async function getBackendMeta(url: string): Promise<MetaResponse> {
return ofetch<MetaResponse>("/meta", {
baseURL: url,
export function getBackendMeta(url: string): Promise<MetaResponse> {
return f<MetaResponse>("/meta", {
baseUrl: url,
});
}

View File

@@ -1,5 +1,3 @@
import { ofetch } from "ofetch";
import type {
AccountWithToken,
ProgressInput,
@@ -8,6 +6,7 @@ import type {
ProgressUpdateItem,
} from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export function progressUpdateItemToInput(
item: ProgressUpdateItem,
@@ -72,12 +71,12 @@ export async function setProgress(
account: AccountWithToken,
input: ProgressInput,
) {
return ofetch<ProgressResponse>(
return f<ProgressResponse>(
`/users/${account.userId}/progress/${input.tmdbId}`,
{
method: "PUT",
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
body: input,
},
);
@@ -90,10 +89,10 @@ export async function removeProgress(
episodeId?: string,
seasonId?: string,
) {
await ofetch(`/users/${account.userId}/progress/${id}`, {
await f(`/users/${account.userId}/progress/${id}`, {
method: "DELETE",
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
body: {
episodeId,
seasonId,

View File

@@ -1,22 +1,21 @@
import { ofetch } from "ofetch";
import type {
ChallengeTokenResponse,
RegisterInput,
SessionResponse,
UserResponse,
} from "./types";
import { f } from "./fetch";
export async function getRegisterChallengeToken(
url: string,
captchaToken?: string,
): Promise<ChallengeTokenResponse> {
return ofetch<ChallengeTokenResponse>("/auth/register/start", {
return f<ChallengeTokenResponse>("/auth/register/start", {
method: "POST",
body: {
captchaToken,
},
baseURL: url,
baseUrl: url,
});
}
@@ -30,12 +29,12 @@ export async function registerAccount(
url: string,
data: RegisterInput,
): Promise<RegisterResponse> {
return ofetch<RegisterResponse>("/auth/register/complete", {
return f<RegisterResponse>("/auth/register/complete", {
method: "POST",
body: {
namespace: "movie-web",
...data,
},
baseURL: url,
baseUrl: url,
});
}

View File

@@ -1,12 +1,11 @@
import { ofetch } from "ofetch";
import type { AccountWithToken, SessionResponse, SessionUpdate } from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export async function getSessions(url: string, account: AccountWithToken) {
return ofetch<SessionResponse[]>(`/users/${account.userId}/sessions`, {
return f<SessionResponse[]>(`/users/${account.userId}/sessions`, {
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
});
}
@@ -15,11 +14,11 @@ export async function updateSession(
account: AccountWithToken,
update: SessionUpdate,
) {
return ofetch<SessionResponse[]>(`/sessions/${account.sessionId}`, {
return f<SessionResponse[]>(`/sessions/${account.sessionId}`, {
method: "PATCH",
headers: getAuthHeaders(account.token),
body: update,
baseURL: url,
baseUrl: url,
});
}
@@ -28,9 +27,9 @@ export async function removeSession(
token: string,
sessionId: string,
) {
return ofetch<SessionResponse[]>(`/sessions/${sessionId}`, {
return f<SessionResponse[]>(`/sessions/${sessionId}`, {
method: "DELETE",
headers: getAuthHeaders(token),
baseURL: url,
baseUrl: url,
});
}

View File

@@ -1,29 +1,28 @@
import { ofetch } from "ofetch";
import type {
AccountWithToken,
SettingsInput,
SettingsResponse,
} from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export function updateSettings(
url: string,
account: AccountWithToken,
settings: SettingsInput,
) {
return ofetch<SettingsResponse>(`/users/${account.userId}/settings`, {
return f<SettingsResponse>(`/users/${account.userId}/settings`, {
method: "PUT",
body: settings,
baseURL: url,
baseUrl: url,
headers: getAuthHeaders(account.token),
});
}
export function getSettings(url: string, account: AccountWithToken) {
return ofetch<SettingsResponse>(`/users/${account.userId}/settings`, {
return f<SettingsResponse>(`/users/${account.userId}/settings`, {
method: "GET",
baseURL: url,
baseUrl: url,
headers: getAuthHeaders(account.token),
});
}

View File

@@ -1,5 +1,3 @@
import { ofetch } from "ofetch";
import type {
AccountWithToken,
BookmarkMediaItem,
@@ -11,6 +9,7 @@ import type {
UserResponse,
} from "./types";
import { getAuthHeaders } from "./auth";
import { f } from "./fetch";
export function bookmarkResponsesToEntries(responses: BookmarkResponse[]) {
const entries = responses.map((bookmark) => {
@@ -83,13 +82,10 @@ export async function getUser(
url: string,
token: string,
): Promise<{ user: UserResponse; session: SessionResponse }> {
return ofetch<{ user: UserResponse; session: SessionResponse }>(
"/users/@me",
{
headers: getAuthHeaders(token),
baseURL: url,
},
);
return f<{ user: UserResponse; session: SessionResponse }>("/users/@me", {
headers: getAuthHeaders(token),
baseUrl: url,
});
}
export async function editUser(
@@ -97,13 +93,13 @@ export async function editUser(
account: AccountWithToken,
object: UserEdit,
): Promise<{ user: UserResponse; session: SessionResponse }> {
return ofetch<{ user: UserResponse; session: SessionResponse }>(
return f<{ user: UserResponse; session: SessionResponse }>(
`/users/${account.userId}`,
{
method: "PATCH",
headers: getAuthHeaders(account.token),
body: object,
baseURL: url,
baseUrl: url,
},
);
}
@@ -112,22 +108,22 @@ export async function deleteUser(
url: string,
account: AccountWithToken,
): Promise<UserResponse> {
return ofetch<UserResponse>(`/users/${account.userId}`, {
return f<UserResponse>(`/users/${account.userId}`, {
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
});
}
export async function getBookmarks(url: string, account: AccountWithToken) {
return ofetch<BookmarkResponse[]>(`/users/${account.userId}/bookmarks`, {
return f<BookmarkResponse[]>(`/users/${account.userId}/bookmarks`, {
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
});
}
export async function getProgress(url: string, account: AccountWithToken) {
return ofetch<ProgressResponse[]>(`/users/${account.userId}/progress`, {
return f<ProgressResponse[]>(`/users/${account.userId}/progress`, {
headers: getAuthHeaders(account.token),
baseURL: url,
baseUrl: url,
});
}