diff --git a/apps/expo/src/app/sync/trust/[url].tsx b/apps/expo/src/app/sync/trust/[url].tsx
index ba03c54..9f64eaa 100644
--- a/apps/expo/src/app/sync/trust/[url].tsx
+++ b/apps/expo/src/app/sync/trust/[url].tsx
@@ -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"
>
I trust this server
- Go back
+ Go back
Already have an account?{" "}
diff --git a/apps/expo/src/components/ui/Button.tsx b/apps/expo/src/components/ui/Button.tsx
index 7399765..c19f48c 100644
--- a/apps/expo/src/components/ui/Button.tsx
+++ b/apps/expo/src/components/ui/Button.tsx
@@ -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",
},
},
},
diff --git a/packages/api/package.json b/packages/api/package.json
index 075d0c6..d0a27eb 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -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"
}
}
diff --git a/packages/api/src/auth.ts b/packages/api/src/auth.ts
index 99b6448..703070e 100644
--- a/packages/api/src/auth.ts
+++ b/packages/api/src/auth.ts
@@ -1,6 +1,5 @@
-import { ofetch } from "ofetch";
-
import type { LoginResponse } from "./types";
+import { f } from "./fetch";
export function getAuthHeaders(token: string): Record {
return {
@@ -13,12 +12,12 @@ export async function accountLogin(
id: string,
deviceName: string,
): Promise {
- return ofetch("/auth/login", {
+ return f("/auth/login", {
method: "POST",
body: {
id,
device: deviceName,
},
- baseURL: url,
+ baseUrl: url,
});
}
diff --git a/packages/api/src/bookmarks.ts b/packages/api/src/bookmarks.ts
index b9194b3..4a43844 100644
--- a/packages/api/src/bookmarks.ts
+++ b/packages/api/src/bookmarks.ts
@@ -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(
+ return f(
`/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,
+ });
}
diff --git a/packages/api/src/crypto.ts b/packages/api/src/crypto.ts
index 45d6b41..67f4e2a 100644
--- a/packages/api/src/crypto.ts
+++ b/packages/api/src/crypto.ts
@@ -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 {
+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);
}
diff --git a/packages/api/src/fetch.ts b/packages/api/src/fetch.ts
new file mode 100644
index 0000000..c0260b4
--- /dev/null
+++ b/packages/api/src/fetch.ts
@@ -0,0 +1,53 @@
+export interface FetcherOptions {
+ baseUrl?: string;
+ headers?: Record;
+ query?: Record;
+ method?: "HEAD" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
+ readHeaders?: string[];
+ body?: Record;
+}
+
+export type FullUrlOptions = Pick;
+
+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(url: string, ops?: FetcherOptions): Promise {
+ 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;
+}
diff --git a/packages/api/src/import.ts b/packages/api/src/import.ts
index 7d6dcca..a63fd9b 100644
--- a/packages/api/src/import.ts
+++ b/packages/api/src/import.ts
@@ -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(`/users/${account.userId}/progress/import`, {
+ return f(`/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(`/users/${account.userId}/bookmarks`, {
+ return f(`/users/${account.userId}/bookmarks`, {
method: "PUT",
body: bookmarks,
- baseURL: url,
+ baseUrl: url,
headers: getAuthHeaders(account.token),
});
}
diff --git a/packages/api/src/login.ts b/packages/api/src/login.ts
index 5408b10..3029eea 100644
--- a/packages/api/src/login.ts
+++ b/packages/api/src/login.ts
@@ -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 {
- return ofetch("/auth/login/start", {
+ return f("/auth/login/start", {
method: "POST",
body: {
publicKey,
},
- baseURL: url,
+ baseUrl: url,
});
}
@@ -23,12 +22,12 @@ export async function loginAccount(
url: string,
data: LoginInput,
): Promise {
- return ofetch("/auth/login/complete", {
+ return f("/auth/login/complete", {
method: "POST",
body: {
namespace: "movie-web",
...data,
},
- baseURL: url,
+ baseUrl: url,
});
}
diff --git a/packages/api/src/meta.ts b/packages/api/src/meta.ts
index b2f8620..cca92e4 100644
--- a/packages/api/src/meta.ts
+++ b/packages/api/src/meta.ts
@@ -1,9 +1,8 @@
-import { ofetch } from "ofetch";
-
import type { MetaResponse } from "./types";
+import { f } from "./fetch";
-export async function getBackendMeta(url: string): Promise {
- return ofetch("/meta", {
- baseURL: url,
+export function getBackendMeta(url: string): Promise {
+ return f("/meta", {
+ baseUrl: url,
});
}
diff --git a/packages/api/src/progress.ts b/packages/api/src/progress.ts
index 84fb818..27da773 100644
--- a/packages/api/src/progress.ts
+++ b/packages/api/src/progress.ts
@@ -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(
+ return f(
`/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,
diff --git a/packages/api/src/register.ts b/packages/api/src/register.ts
index d66444c..4ac09bc 100644
--- a/packages/api/src/register.ts
+++ b/packages/api/src/register.ts
@@ -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 {
- return ofetch("/auth/register/start", {
+ return f("/auth/register/start", {
method: "POST",
body: {
captchaToken,
},
- baseURL: url,
+ baseUrl: url,
});
}
@@ -30,12 +29,12 @@ export async function registerAccount(
url: string,
data: RegisterInput,
): Promise {
- return ofetch("/auth/register/complete", {
+ return f("/auth/register/complete", {
method: "POST",
body: {
namespace: "movie-web",
...data,
},
- baseURL: url,
+ baseUrl: url,
});
}
diff --git a/packages/api/src/sessions.ts b/packages/api/src/sessions.ts
index 79bb9c7..8d996e1 100644
--- a/packages/api/src/sessions.ts
+++ b/packages/api/src/sessions.ts
@@ -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(`/users/${account.userId}/sessions`, {
+ return f(`/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(`/sessions/${account.sessionId}`, {
+ return f(`/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(`/sessions/${sessionId}`, {
+ return f(`/sessions/${sessionId}`, {
method: "DELETE",
headers: getAuthHeaders(token),
- baseURL: url,
+ baseUrl: url,
});
}
diff --git a/packages/api/src/settings.ts b/packages/api/src/settings.ts
index b28b715..78bcdcd 100644
--- a/packages/api/src/settings.ts
+++ b/packages/api/src/settings.ts
@@ -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(`/users/${account.userId}/settings`, {
+ return f(`/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(`/users/${account.userId}/settings`, {
+ return f(`/users/${account.userId}/settings`, {
method: "GET",
- baseURL: url,
+ baseUrl: url,
headers: getAuthHeaders(account.token),
});
}
diff --git a/packages/api/src/user.ts b/packages/api/src/user.ts
index 8d2e957..57c7667 100644
--- a/packages/api/src/user.ts
+++ b/packages/api/src/user.ts
@@ -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 {
- return ofetch(`/users/${account.userId}`, {
+ return f(`/users/${account.userId}`, {
headers: getAuthHeaders(account.token),
- baseURL: url,
+ baseUrl: url,
});
}
export async function getBookmarks(url: string, account: AccountWithToken) {
- return ofetch(`/users/${account.userId}/bookmarks`, {
+ return f(`/users/${account.userId}/bookmarks`, {
headers: getAuthHeaders(account.token),
- baseURL: url,
+ baseUrl: url,
});
}
export async function getProgress(url: string, account: AccountWithToken) {
- return ofetch(`/users/${account.userId}/progress`, {
+ return f(`/users/${account.userId}/progress`, {
headers: getAuthHeaders(account.token),
- baseURL: url,
+ baseUrl: url,
});
}