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
14 changes: 12 additions & 2 deletions projects/developer/src/lib/api/accountRequest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { accessToken } from '$lib/auth/accessToken.ts';
import { developerErrorMessage } from './developerErrorMessage.ts';
import { traktHeaders } from './traktHeaders.ts';

async function errorCode(response: Response): Promise<unknown> {
const body = await response.json().catch(() => null) as
| { error?: unknown }
| null;
return body?.error;
}

// Account requests bypass playground history, response previews, and storage.
export async function accountRequest(
slot: number,
Expand All @@ -18,9 +26,10 @@ export async function accountRequest(
redirect: 'error',
});
if (!response.ok) {
const localized = developerErrorMessage(await errorCode(response));
const messages: Record<number, string> = {
400:
'Your GitHub connection could not be verified. Reconnect and try again.',
'The request could not be accepted. Check your details and try again.',
401: 'Your session has expired. Refresh your account or sign in again.',
403:
'This account cannot perform this action. Check your app limit and your GitHub account connection.',
Expand All @@ -29,7 +38,8 @@ export async function accountRequest(
429: 'Too many requests. Please wait before trying again.',
};
throw new Error(
messages[response.status] ??
localized ??
messages[response.status] ??
`The request failed (${response.status}). Please try again.`,
);
}
Expand Down
40 changes: 40 additions & 0 deletions projects/developer/src/lib/api/developerErrorMessage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest';
import { developerErrorMessage } from './developerErrorMessage.ts';

const API_ERROR_CODES = [
'authentication_required',
'invalid_request_body',
'invalid_application_id',
'invalid_redirect_uri',
'invalid_origin',
'redirect_uris_too_long',
'origins_too_long',
'application_limit_reached',
'application_not_found',
'application_create_failed',
'github_required',
'github_not_linked',
'github_unavailable',
'github_code_expired',
'github_account_taken',
'github_account_mismatch',
];

describe('developerErrorMessage', () => {
it.each(API_ERROR_CODES)('has copy for %s', (code) => {
const message = developerErrorMessage(code);
expect(message).toBeTruthy();
expect(message).not.toBe(code);
});
it('returns null for an unknown code', () => {
expect(developerErrorMessage('something_new')).toBeNull();
});
it('returns null for anything that is not a string', () => {
expect(developerErrorMessage(undefined)).toBeNull();
expect(developerErrorMessage({ secret: 'x' })).toBeNull();
});
it('does not resolve inherited object keys', () => {
expect(developerErrorMessage('toString')).toBeNull();
expect(developerErrorMessage('__proto__')).toBeNull();
});
});
34 changes: 34 additions & 0 deletions projects/developer/src/lib/api/developerErrorMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const DEVELOPER_ERROR_MESSAGES = {
authentication_required:
'Your session has expired. Refresh your account or sign in again.',
invalid_request_body:
'The request could not be accepted. Check your details and try again.',
invalid_application_id: 'This app could not be found. Reload your apps.',
invalid_redirect_uri: 'Enter valid redirect URIs, one per line.',
invalid_origin: 'Enter valid HTTP or HTTPS origins, one per line.',
redirect_uris_too_long:
'Enter 1-25 redirect URIs, within 2,048 characters in total.',
origins_too_long: 'Enter up to 25 origins, within 255 characters in total.',
application_limit_reached: 'You have reached the app limit for your account.',
application_not_found: 'This app is no longer available. Reload your apps.',
application_create_failed: 'The app could not be created. Please try again.',
github_required: 'Connect your GitHub account before creating an app.',
github_not_linked: 'No GitHub account is linked to this Trakt account.',
github_unavailable:
'GitHub is not responding right now. Please try again in a moment.',
github_code_expired:
'The GitHub connection expired before it finished. Connect again.',
github_account_taken:
'This GitHub account is already connected to another Trakt account.',
github_account_mismatch:
'Your Trakt account is connected to a different GitHub account. Use Switch GitHub account to change it.',
} as const;

export type DeveloperErrorCode = keyof typeof DEVELOPER_ERROR_MESSAGES;

export function developerErrorMessage(code: unknown): string | null {
return typeof code === 'string' &&
Object.hasOwn(DEVELOPER_ERROR_MESSAGES, code)
? DEVELOPER_ERROR_MESSAGES[code as DeveloperErrorCode]
: null;
}
91 changes: 15 additions & 76 deletions projects/developer/src/lib/features/apps/ApplicationForm.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
<script lang="ts">
import { onMount } from "svelte";
import type { Application, ApplicationInput } from "./applications.ts";
import { parseApplication } from "./validateApplication.ts";
import {
githubConnectUrl,
takeGithubCode,
takeGithubDraft,
} from "./githubConnect.ts";
const {
app,
linkedGithubUsername,
githubUsername = null,
busy,
onSave,
onCancel,
}: {
app?: Application;
linkedGithubUsername: string | null;
githubUsername?: string | null;
busy: boolean;
onSave: (input: ApplicationInput) => void;
onCancel: () => void;
Expand All @@ -27,45 +21,10 @@
let redirects = $state(initial?.redirect_uri ?? "");
let origins = $state(initial?.origins.join("\n") ?? "");
let error = $state("");
const githubUsername = $derived(
initial?.github_username ?? linkedGithubUsername ?? null,
);
let githubCode = $state<string | null>(null);
onMount(() => {
const code = takeGithubCode();
if (!code) return;
githubCode = code;
const draft = takeGithubDraft();
if (!draft) return;
name = draft.name;
description = draft.description;
redirects = draft.redirects;
origins = draft.origins;
});
function connectGithub() {
globalThis.location.assign(
githubConnectUrl(globalThis.location.pathname, {
name,
description,
redirects,
origins,
}),
);
}
function submit(event: SubmitEvent) {
event.preventDefault();
if (!app && !githubCode && !githubUsername) {
error = "Connect your GitHub account before creating an app.";
return;
}
try {
const input = parseApplication(
name,
description,
redirects,
origins,
githubCode ?? undefined,
);
const input = parseApplication(name, description, redirects, origins);
error = "";
onSave(input);
} catch (cause) {
Expand All @@ -76,6 +35,10 @@

<form onsubmit={submit}>
<fieldset disabled={busy}>
{#if !app && githubUsername}<p class="creating-as">
Creating as <strong>@{githubUsername}</strong>, your verified GitHub
account.
</p>{/if}
<label
>App name <input
bind:value={name}
Expand Down Expand Up @@ -110,27 +73,6 @@
placeholder="https://example.com"
spellcheck="false"></textarea></label
>
<div class="github-connect">
<strong>GitHub account</strong>
{#if githubCode}
<span
>GitHub connected. Save to {app ? "update" : "attach"} your handle.</span
>
{:else if githubUsername}
<span>Connected as <strong>@{githubUsername}</strong></span>
{:else}
<span
>{app
? "Connect a GitHub account to verify this app."
: "Connect a GitHub account once to verify who you are."}</span
>
{/if}
<button type="button" onclick={connectGithub}
>{githubUsername && !githubCode
? "Re-verify"
: "Connect GitHub"}</button
>
</div>
{#if !app}<p>
By creating an app, you agree to the <a
href="/?section=guides&guide=create-an-app">Trakt API requirements</a
Expand Down Expand Up @@ -167,18 +109,15 @@
font-weight: 400;
line-height: 1.6;
}
.github-connect {
display: grid;
gap: 9px;
font-size: 14px;
font-weight: 600;
border: 1px solid var(--color-border);
.creating-as {
margin: 0;
padding: 12px 16px;
border-radius: var(--radius-control);
padding: 16px;
}
.github-connect button {
@include action.base;
justify-self: start;
border: 1px solid
color-mix(in srgb, var(--color-success) 32%, var(--color-border));
background: color-mix(in srgb, var(--color-success) 9%, transparent);
color: var(--color-foreground);
font-size: 13px;
}
input,
textarea {
Expand Down
Loading
Loading