Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(DRAFT) Developer site #1076

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
blog/ @jellyfin/core
docs/general/about.md @jellyfin/core
docs/general/community-standards.md @joshuaboniface
content/developers/ @nielsvanvelzen
6 changes: 6 additions & 0 deletions content/developers/docs/_sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
developers: [
{ type: 'autogenerated', dirName: '.' },
{ type: 'link', label: 'API Documentation', href: 'https://api.jellyfin.org' }
]
};
2 changes: 2 additions & 0 deletions content/developers/docs/api/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label: 'HTTP API'
position: 2
72 changes: 72 additions & 0 deletions content/developers/docs/api/authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Authorization
sidebar_position: 2
---

# Authorization

To start using the Jellyfin API, authorization is probably the first thing you'll need to do. Jellyfin's authorization options can be a bit confusing because there are a lot of deprecated options.

Generally there are three ways to authenticate: no authorization, user authorization with an access token or authorization with an API key. The first way is easy, just do nothing. But most often you'll need to use either the access token or API key.
felix920506 marked this conversation as resolved.
Show resolved Hide resolved

## Sending authorization values

There are multiple methods for transmitting authorization values, however, some are outdated and scheduled to be removed.
It's recommend to use the `Authorization` header. If header auth isn't an option, the token should be sent through the `ApiKey` query parameter.

| Type | Name | Method | Deprecated |
| ------ | ---------------------- | ---------- | ---------- |
| Header | `Authorization` | Schema | No |
| Query | `ApiKey` | Token only | No |
| Query | `api_key` | Token only | **yes** |
| Header | `X-Emby-Token` | Token only | **yes** |
| Header | `X-MediaBrowser-Token` | Token only | **yes** |
| Header | `X-Emby-Authorization` | Schema | **yes** |

Avoid sending multiple tokens in one request as it's uncertain which value will be used. Deprecated options might be removed in future server updates.

## The Jellyfin authorization scheme

The [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) uses the format `Authorization: <scheme> <parameters>`. The Jellyfin scheme is named `MediaBrowser` and it uses named values separated by commas. There is no specific order for parameters. All keys are case sensitive and only allow alphanumeric characters. Unknown keys are ignored by the server. Values must be wrapped in double quotes (`"`) and should use [url encoding](https://en.wikipedia.org/wiki/URL_encoding).

```txt
MediaBrowser key="value", key2="value2", key3="value3"
```

### Parameters

| Key | Description |
| -------- | -------------------------------------------------- |
| Token | The access token or API key |
| Client | The name of the client |
| Version | The version of the client |
| DeviceId | A unique id for the device generated by the client |
| Device | The device name |

The token parameter is required to use authenticated endpoints. The client and version properties are used to identify the client in the dashboard.

#### Device identifiers

When it comes to device identifiers in the Jellyfin API, it's important to understand the nuances involved. While generating a random string for the `deviceId` might seem like a straightforward solution, there are certain limitations to consider. Currently, the server permits only a single access token for each `deviceId`. This means that you cannot have multiple users signed into your client with a single randomized string. To work around this limitation, you'll need to use a unique identifier for each combination of a device-specific identifier and user-specific identifier.

Since it's often not possible to know the user identifier before signing in at least once, we recommend including the username as user-specific identifier. It is advisable to hash the username because it is user-input that could include double quotes (escaping the header value format) or special characters that your HTTP library might not allow.

### Examples

Here are a couple of examples for the authorization header:

- Authenticate with API key

```http
Authorization: MediaBrowser Token="8ac3a7abaff943ba9adea7f8754da7f8"
```

- Authenticate with access token

```http
Authorization: MediaBrowser Token="0381cf931f9e42d79fb9c89f729167df", Client="Android TV", Device="Nvidia Shield", DeviceId="ZQ9YQHHrUzk24vV", Version="0.15.3"
```

## The ApiKey query parameter

Use the `ApiKey` query parameter when the `Authorization` header can't be used. The value of the query parameter is the access token or API key. Avoid using this option if possible. Never use the `ApiKey` query parameter and the `Authorization` header at the same time.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
uid: contrib-branding
title: Branding
title: Jellyfin branding
sidebar_position: 3
---

# Branding
Expand Down
2 changes: 2 additions & 0 deletions content/developers/docs/contributing/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label: 'Contributing'
position: 4
2 changes: 2 additions & 0 deletions content/developers/docs/style-guides/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label: 'Style guides'
position: 5
16 changes: 16 additions & 0 deletions content/developers/pages/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.card--dev-index {
h2 {
margin: 10px 20px 0 20px;
}

ul {
padding: 0;
list-style: none;
margin: 10px 10px 0 10px;

li a {
display: block;
padding: 10px;
}
}
}
49 changes: 49 additions & 0 deletions content/developers/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import Hero from '../../../src/components/common/Hero';
import blogPosts from '../../../.docusaurus/docusaurus-plugin-content-blog/developers-blog/blog-post-list-prop-developers-blog.json';

import './index.scss';
import NavigationLinks from '../../../src/components/navigationLinks/NavigationLinks';

export default function Index() {
return (
<Layout title={`Jellyfin developers`}>
<Hero title='Jellyfin for Developers'>
<p className='hero__text margin-vert--lg'>Get started developing with or for Jellyfin today.</p>
<div className='hero__buttons'>
<Link to='/docs/'>Not a developer? Go to the user documentation</Link>
</div>
</Hero>
<main>
<section className='container'>
<div className='row'>
<NavigationLinks
title='Documentation'
pages={[
{ url: '/developers/docs/api/authorization', name: 'Using the REST API' },
{ url: '/developers/docs/api/authorization', name: 'Creating a server plugin' }
]}
/>

<NavigationLinks
title={blogPosts.title}
pages={blogPosts.items.slice(0, 5).map((item) => {
return { url: item.permalink, name: item.title };
})}
/>

<NavigationLinks
title='More resources'
pages={[
{ url: '/developers/docs/contributing', name: 'Contributing' },
{ url: '/developers/docs/branding', name: 'Jellyfin branding' }
]}
/>
</div>
</section>
</main>
</Layout>
);
}
52 changes: 43 additions & 9 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,23 @@ const config: Config = {
src: 'images/logo.svg'
},
items: [
{ to: 'posts', label: 'Blog', position: 'right' },
{
to: 'downloads',
label: 'Downloads',
position: 'right'
label: 'Downloads'
},
{
to: 'contribute',
label: 'Contribute',
position: 'right'
to: 'posts',
label: 'Blog'
},
{
type: 'doc',
docId: 'index',
label: 'Documentation',
type: 'docSidebar',
sidebarId: 'docs',
label: 'Documentation'
},

{
to: 'developers',
label: 'Developers',
position: 'right'
},
{
Expand Down Expand Up @@ -123,6 +125,38 @@ Site content is licensed <a href='http://creativecommons.org/licenses/by-nd/4.0/
} satisfies Blog.Options
],
['@docusaurus/plugin-content-pages', {} satisfies Pages.Options],
// Developers content
[
'@docusaurus/plugin-content-docs',
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: 'developers-docs',
path: 'content/developers/docs',
routeBasePath: 'developers/docs',
sidebarPath: require.resolve('./content/developers/docs/_sidebar.js'),
editUrl: 'https://github.com/jellyfin/jellyfin.org/edit/master/'
}
],
[
'@docusaurus/plugin-content-blog',
/** @type {import('@docusaurus/plugin-content-blog').Options} */
{
id: 'developers-blog',
path: 'content/developers/blog',
routeBasePath: 'developers/blog',
showReadingTime: true,
authorsMapPath: '../../../blog/authors.yml'
}
],
[
'@docusaurus/plugin-content-pages',
/** @type {import('@docusaurus/plugin-content-pages').Options} */
{
id: 'developers-pages',
path: 'content/developers/pages',
routeBasePath: 'developers'
}
],
// Others
['@docusaurus/plugin-sitemap', {} satisfies Sitemap.Options],
['docusaurus-plugin-sass', {}],
Expand Down
33 changes: 33 additions & 0 deletions redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ const redirects: ClientRedirects.Options['redirects'] = [
{
from: '/docs/general/administration/building',
to: '/docs/general/installation/source'
},
// New developer site
{
from: '/docs/general/contributing/branding',
to: '/developers/docs/branding'
},
{
from: '/docs/general/contributing/',
to: '/developers/docs/contributing/'
},
{
from: '/docs/general/contributing/development',
to: '/developers/docs/contributing/development'
},
{
from: '/docs/general/contributing/issues',
to: '/developers/docs/contributing/issues'
},
{
from: '/docs/general/contributing/release-procedure',
to: '/developers/docs/contributing/release-procedure'
},
{
from: '/docs/general/contributing/source-tree',
to: '/developers/docs/contributing/source-tree'
},
{
from: '/docs/general/style-guides/',
to: '/developers/docs/style-guides/'
},
{
from: '/docs/general/style-guides/javascript',
to: '/developers/docs/style-guides/javascript'
}
];
export default redirects;
3 changes: 1 addition & 2 deletions sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ const sidebars: SidebarConfig = {
docs: [
// "docs/general" pages
'index',
{ type: 'autogenerated', dirName: 'general' },
{ type: 'link', label: 'API Documentation', href: 'https://api.jellyfin.org' }
{ type: 'autogenerated', dirName: 'general' }
]
};

Expand Down
5 changes: 5 additions & 0 deletions src/components/banner/Banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.banner {
background-color: #007ca6;
padding: 10px;
text-align: center;
}
9 changes: 9 additions & 0 deletions src/components/banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

import './Banner.scss';

function Banner({ text }: { text: string }) {
return <div className='banner'>{text}</div>;
}

export default Banner;
23 changes: 23 additions & 0 deletions src/components/navigationLinks/NavigationLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import Section from '../section/Section';
import Link from '@docusaurus/Link';

interface Page {
url: string;
name: string;
}

export default function NavigationLinks({ title, pages }: { title: string; pages: Page[] }) {
return (
<Section title={title}>
<ul>
{pages.map((page, index) => (
<li key={index}>
<Link to={page.url}>{page.name}</Link>
</li>
))}
</ul>
</Section>
);
}
12 changes: 12 additions & 0 deletions src/components/section/Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { ReactNode } from 'react';

export default function Section({ title, children }: { title: string; children: ReactNode }) {
return (
<div className='col col--4 margin-top--md margin-bottom--md'>
<div className='card card--dev-index'>
<h2>{title}</h2>
{children}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion src/pages/contribute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function Contribute() {
<p>
Before contributing, please read over our{' '}
<Link to='/docs/general/community-standards'>Community&nbsp;Standards</Link> and&nbsp;
<Link to='/docs/general/contributing'>Contributing&nbsp;Guide</Link>.
<Link to='/developers/docs/contributing/'>Contributing&nbsp;Guide</Link>.
</p>
</section>

Expand Down
19 changes: 19 additions & 0 deletions src/theme/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { useLocation } from '@docusaurus/router';
import Banner from '../components/banner/Banner';

/**
* The root view wrapper for the entire app.
*/
export default function Root({ children }: { children: React.ReactNode }) {
const isInDevelopersSection = useLocation().pathname.startsWith('/developers');

return (
<>
{isInDevelopersSection && (
<Banner text='The developer site is still in development. Some content may be incomplete or missing.' />
)}
{children}
</>
);
}