Skip to content

Commit fdb0400

Browse files
committed
Added BlazorWASM example
1 parent ebc5278 commit fdb0400

35 files changed

+1407
-40
lines changed

SpotifyAPI.Docs/docs/authorization_code.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ public Task GetCallback(string code)
4141
If the token expires at some point (check via `response.IsExpired`), you can refresh it:
4242

4343
```csharp
44-
var newResponse = await new OAuthClient().RequestToken(
45-
new AuthorizationCodeRefreshRequest("ClientId", "ClientSecret", response.RefreshToken)
46-
);
44+
var newResponse = await new OAuthClient().RequestToken(
45+
new AuthorizationCodeRefreshRequest("ClientId", "ClientSecret", response.RefreshToken)
46+
);
4747

48-
var spotify = new SpotifyClient(newResponse.AccessToken);
48+
var spotify = new SpotifyClient(newResponse.AccessToken);
4949
```
5050

5151
You can also let the `AuthorizationCodeAuthenticator` take care of the refresh part:

SpotifyAPI.Docs/docs/example_asp.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_asp
3+
title: ASP.NET
4+
---
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_blazor
3+
title: ASP.NET Blazor
4+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_cli_custom_html
3+
title: CLI - Custom HTML
4+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_cli_persistent_config
3+
title: CLI - Persistent Config
4+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_token_swap
3+
title: Token Swap
4+
---

SpotifyAPI.Docs/docs/example_uwp.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
id: example_uwp
3+
title: UWP
4+
---

SpotifyAPI.Docs/docs/showcase.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
id: showcase
3+
title: Showcase
4+
---
5+
6+
:::info
7+
Are you using `SpotifyAPI-NET` and would like to include your project in this list?
8+
9+
Send a PR via the "Edit this page" link at the end of the page!
10+
:::
11+
12+
### [lidarr](https://github.com/lidarr/Lidarr) by [@lidarr](https://github.com/lidarr)
13+
14+
> Looks and smells like Sonarr but made for music.
15+
16+
### [botframework-solutions](https://github.com/microsoft/botframework-solutions) by [@microsoft](https://github.com/microsoft)
17+
18+
> home for a set of templates and solutions to help build advanced conversational experiences using Azure Bot Service and Bot Framework
19+
20+
### [Spytify](https://github.com/jwallet/spy-spotify) by [@jwallet](https://github.com/jwallet)
21+
22+
> Records Spotify to mp3 without ads while it plays and includes media tags to the recorded files
23+
24+
### [audio-band](https://github.com/dsafa/audio-band) by [@dsafa](https://github.com/dsafa)
25+
26+
> Display and control songs from the Windows taskbar
27+
28+
### [rocksmith-custom-song-toolkit](https://github.com/catara/rocksmith-custom-song-toolkit) by [@catara](https://github.com/catara)
29+
30+
> MASS Manipulation of Rocksmith DLC Library
31+
32+
### [Spofy](https://github.com/eltoncezar/Spofy) by [@eltoncezar](https://github.com/eltoncezar)
33+
34+
> A Spotify mini player and notifier for Windows
35+
36+
### [Toastify](https://github.com/aleab/toastify) by [@aleab](https://github.com/aleab)
37+
38+
> Toastify adds global hotkeys and toast notifications to Spotify
39+
>
40+
> *Forked from [nachmore/toastify](https://github.com/nachmore/toastify)*
41+
42+
### [Spotify Oculus](https://github.com/CaptainMorgs/spotify-oculus-release) by [@CaptainMorgs](https://github.com/CaptainMorgs)
43+
44+
> Unity project for interacting with Spotify in virtual reality for the Oculus Rift.
45+
46+
### [Songify](https://github.com/Inzaniity/Songify) by [@Inzaniity](https://github.com/Inzaniity)
47+
48+
> A simple tool that gets the current track from Spotify, YouTube and Nightbot.
49+
50+
### [Elite G19s Companion app](https://forums.frontier.co.uk/threads/elite-g19s-companion-app-with-simulated-space-traffic-control.226782/) by [@MagicMau](https://github.com/MagicMau)
51+
52+
> Main features include: system and station overview, play radio and podcast with audio visualizations, simulated Space Traffic Control, GPS functionality (including planetary races), an orrery view, a screenshot converter, and a news ticker.
53+
54+
### [ARDUINO-Spotify-Remote-Control](https://github.com/NADER11NDEU/ARDUINO-Spotify-Remote-Control) by [@NADER11NDEU](https://github.com/NADER11NDEU)
55+
56+
> Well, with this project we will be able to control active spotify devices with Arduino. How we gonna do that ? We will use serial communication.

SpotifyAPI.Docs/docs/token_swap.md

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
id: token_swap
3+
title: Token Swap
4+
---
5+
6+
Token Swap provides an authenticatiow flow where client-side apps (like cli/desktop/mobile apps) are still able to use long-living tokens and the oppurtunity to refresh them without exposing your application's secret. This however requires a server-side part to work.
7+
8+
It is based on the [Authorization Code](authorization_code) flow and is also documented by spotify: [Token Swap and Refresh ](https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/).
9+
10+
## Flow
11+
12+
The client uses the first part of the `Authorization Code` flow and redirects the user to spotify's login page. In this part, only the client id is required. Once the user logged in and confirmed the usage of your app, he will be redirect to a `http://localhost` server which grabs the `code` from the query parameters.
13+
14+
```csharp
15+
var request = new LoginRequest("http://localhost", "ClientId", LoginRequest.ResponseType.Code)
16+
{
17+
Scope = new List<string> { Scopes.UserReadEmail }
18+
};
19+
BrowserUtil.Open(uri);
20+
```
21+
22+
Now, swapping out this `code` for an `access_token` would require the app's client secret. We don't have this on the client-side. Instead, we send a request to our server, which takes care of the code swap:
23+
24+
```csharp
25+
public Task GetCallback(string code)
26+
{
27+
var response = await new OAuthClient().RequestToken(
28+
new TokenSwapTokenRequest("https://your-swap-server.com/swap", code)
29+
);
30+
31+
var spotify = new SpotifyClient(response.AccessToken);
32+
// Also important for later: response.RefreshToken
33+
}
34+
```
35+
36+
The server swapped out the `code` for an `access_token` and `refresh_token`. Once we realize the `access_token` expired, we can also ask the server to refresh it:
37+
38+
```csharp
39+
// if response.IsExpired is true
40+
var newResponse = await new OAuthClient().RequestToken(
41+
new TokenSwapTokenRequest("https://your-swap-server.com/refresh", response.RefreshToken)
42+
);
43+
44+
var spotify = new SpotifyClient(newResponse.AccessToken);
45+
```
46+
47+
## Server Implementation
48+
49+
The server needs to support two endpoints, `/swap` and `/refresh` (endpoints can be named differently of course)
50+
51+
### Swap
52+
53+
The client sends a body via `application/x-www-form-urlencoded` where the received `code` is included. In cURL:
54+
55+
```bash
56+
curl -X POST "https://example.com/v1/swap"\
57+
-H "Content-Type: application/x-www-form-urlencoded"\
58+
--data "code=AQDy8...xMhKNA"
59+
```
60+
61+
The server needs to respond with content-type `application/json` and the at least the following body:
62+
63+
```json
64+
{
65+
"access_token" : "NgAagA...Um_SHo",
66+
"expires_in" : "3600",
67+
"refresh_token" : "NgCXRK...MzYjw"
68+
}
69+
```
70+
71+
### Refresh
72+
73+
The client sends a body via `application/x-www-form-urlencoded` where the received `refresh_token` is included. In cURL:
74+
75+
```bash
76+
curl -X POST "https://example.com/v1/refresh"\
77+
-H "Content-Type: application/x-www-form-urlencoded"\
78+
--data "refresh_token=NgCXRK...MzYjw"
79+
```
80+
81+
The server needs to respond with content-type `application/json` and the at least the following body:
82+
83+
```json
84+
{
85+
"access_token" : "NgAagA...Um_SHo",
86+
"expires_in" : "3600"
87+
}
88+
```
89+
90+
91+
## Example
92+
93+
An example server has been implemented in NodeJS with a .NET CLI client, located at [Example.TokenSwap](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.TokenSwap)

SpotifyAPI.Docs/sidebars.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ module.exports = {
2323
'auth_introduction',
2424
'client_credentials',
2525
'implicit_grant',
26-
'authorization_code'
26+
'authorization_code',
27+
'token_swap'
2728
]
2829
},
30+
'showcase',
2931
{
3032
type: 'category',
3133
label: 'Examples',
32-
items: []
34+
items: [
35+
'example_asp',
36+
'example_blazor',
37+
'example_cli_custom_html',
38+
'example_cli_persistent_config',
39+
'example_token_swap',
40+
'example_uwp'
41+
]
3342
},
3443
]
3544
}

SpotifyAPI.Web.Examples/Example.ASPBlazor/Pages/Index.razor

+32-32
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
<h2>Welcome @_me.DisplayName!</h2>
1010
<p>
11-
You have a grant toal of @_totalPlaylistCount playlists!
11+
You have a grant total of @_totalPlaylistCount playlists!
1212
</p>
1313
}
1414
else
@@ -20,42 +20,42 @@ else
2020
}
2121

2222
@code {
23-
private Uri _authUri;
23+
private Uri _authUri;
2424

25-
private bool _isAuthed;
25+
private bool _isAuthed;
2626

27-
private PrivateUser _me;
27+
private PrivateUser _me;
2828

29-
private int _totalPlaylistCount;
29+
private int _totalPlaylistCount;
3030

31-
protected override void OnInitialized()
32-
{
33-
var clientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID");
34-
var baseUri = navManager.ToAbsoluteUri(navManager.BaseUri);
35-
36-
var loginRequest = new LoginRequest(baseUri, clientId, LoginRequest.ResponseType.Token)
37-
{
38-
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
39-
};
40-
_authUri = loginRequest.ToUri();
41-
}
31+
protected override void OnInitialized()
32+
{
33+
var clientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID");
34+
var baseUri = navManager.ToAbsoluteUri(navManager.BaseUri);
4235

43-
protected override async Task OnInitializedAsync()
36+
var loginRequest = new LoginRequest(baseUri, clientId, LoginRequest.ResponseType.Token)
4437
{
45-
var uri = new Uri(navManager.Uri);
46-
var maxLen = Math.Min(1, uri.Fragment.Length);
47-
Dictionary<string, string> fragmentParams = uri.Fragment.Substring(maxLen)
48-
?.Split("&", StringSplitOptions.RemoveEmptyEntries)
49-
?.Select(param => param.Split("=", StringSplitOptions.RemoveEmptyEntries))
50-
?.ToDictionary(param => param[0], param => param[1]) ?? new Dictionary<string, string>();
51-
52-
_isAuthed = fragmentParams.ContainsKey("access_token");
53-
if (_isAuthed)
54-
{
55-
var spotify = new SpotifyClient(fragmentParams["access_token"]);
56-
57-
_me = await spotify.UserProfile.Current();
58-
_totalPlaylistCount = (await spotify.Playlists.CurrentUsers()).Total;
59-
}
38+
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
39+
};
40+
_authUri = loginRequest.ToUri();
41+
}
42+
43+
protected override async Task OnInitializedAsync()
44+
{
45+
var uri = new Uri(navManager.Uri);
46+
var maxLen = Math.Min(1, uri.Fragment.Length);
47+
Dictionary<string, string> fragmentParams = uri.Fragment.Substring(maxLen)?
48+
.Split("&", StringSplitOptions.RemoveEmptyEntries)?
49+
.Select(param => param.Split("=", StringSplitOptions.RemoveEmptyEntries))?
50+
.ToDictionary(param => param[0], param => param[1]) ?? new Dictionary<string, string>();
51+
52+
_isAuthed = fragmentParams.ContainsKey("access_token");
53+
if (_isAuthed)
54+
{
55+
var spotify = new SpotifyClient(fragmentParams["access_token"]);
56+
57+
_me = await spotify.UserProfile.Current();
58+
_totalPlaylistCount = (await spotify.Playlists.CurrentUsers()).Total;
6059
}
60+
}
6161
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
wwwroot/appsettings.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Router AppAssembly="@typeof(Program).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
4+
</Found>
5+
<NotFound>
6+
<LayoutView Layout="@typeof(MainLayout)">
7+
<p>Sorry, there's nothing at this address.</p>
8+
</LayoutView>
9+
</NotFound>
10+
</Router>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
<RazorLangVersion>3.0</RazorLangVersion>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
10+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
12+
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj" />
17+
</ItemGroup>
18+
19+
</Project>

0 commit comments

Comments
 (0)