Skip to content

Commit c745daa

Browse files
committed
feat: use new api
1 parent 9ab40d4 commit c745daa

13 files changed

Lines changed: 130 additions & 56 deletions

.prettierignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Package Managers
2+
package-lock.json
3+
pnpm-lock.yaml
4+
yarn.lock
5+
public/build

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"printWidth": 100
4+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This is very much a work in progress. Keep your eye on the changelog to see what
1313
1. Connect your KaiOS device to your computer and make sure it appears in WebIDE (Waterfox Classic is recommended)
1414
2. Build the app with `npm run build`
1515
3. In WebIDE, load the `/public` folder as a packaged app
16-
4. On your computer, visit the [RainCloud website](https://app.vulpine.fm/) to sign into SoundCloud
16+
4. On your computer, visit the [RainCloud website](https://raincloud.fm/login/kaios) to sign into SoundCloud
1717
5. In the app, select 'Sign In via QR Code' and scan the code on your screen. \*You may have to exit and reopen the app to scan the code after giving permission
1818
6. When you scan the code, you're in!
1919

package-lock.json

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"lodash.throttle": "^4.1.1",
5252
"numeral": "^2.0.6",
5353
"onyx-ui": "^0.10.0",
54+
"openid-client": "^6.3.3",
5455
"sirv-cli": "^1.0.0",
5556
"svelte-icons": "^2.1.0",
5657
"svelte-spa-router": "^3.2.0"

public/manifest.en-US.webmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
},
5454
"redirects": [
5555
{
56-
"from": "https://app.vulpine.fm/oauth",
56+
"from": "https://raincloud.fm/oauth",
5757
"to": "/index.html"
5858
}
5959
]

public/manifest.webapp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"default_locale": "en-US",
4141
"redirects": [
4242
{
43-
"from": "https://app.vulpine.fm/oauth",
43+
"from": "https://raincloud.fm/oauth",
4444
"to": "/index.html"
4545
}
4646
]

public/manifest.webmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
},
5454
"redirects": [
5555
{
56-
"from": "https://app.vulpine.fm/oauth",
56+
"from": "https://raincloud.fm/oauth",
5757
"to": "/index.html"
5858
}
5959
]

src/App.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import OnyxApp from 'onyx-ui/components/app/OnyxApp.svelte';
33
import { Priority } from 'onyx-ui/enums';
44
import { KeyManager, Onyx } from 'onyx-ui/services';
5-
import Router, { location, pop } from 'svelte-spa-router';
5+
import Router, { location, pop, replace } from 'svelte-spa-router';
66
import AppMenu from './components/AppMenu.svelte';
77
import AudioPlayer from './components/AudioPlayer.svelte';
88
import Dashboard from './components/Dashboard.svelte';
@@ -40,10 +40,10 @@
4040
4141
console.log(`Env: ${process.env.NODE_ENV}`);
4242
43-
const code = window.location.href.match(/code=([A-Za-z0-9-_]+)/)?.[1];
43+
const code = new URL(window.location.href).searchParams.get('code');
4444
if (code) {
4545
new Auth().fetchTokensFromCode(code).then((tokens) => {
46-
window.close();
46+
replace('/library');
4747
});
4848
}
4949
@@ -102,7 +102,7 @@
102102
return true;
103103
},
104104
},
105-
Priority.High
105+
Priority.High,
106106
);
107107
108108
$: Onyx.settings.update($settings);

src/lib/auth.ts

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
import KaiOS from 'kaios-lib';
2+
import { calculatePKCECodeChallenge, randomPKCECodeVerifier, randomState } from 'openid-client';
23
import { push } from 'svelte-spa-router';
34
import type { AuthSession, Tokens, User } from '../models';
45

56
const AUTH_STORAGE_KEY = 'auth_session';
7+
const CODE_VERIFIER_KEY = 'code_verifier';
68

79
type Options = {
810
apiBaseUrl: string;
911
clientId: string;
12+
redirectUri: string;
1013
};
1114
export class Auth {
1215
private options: Options;
1316

1417
constructor(options: Partial<Options> = {}) {
18+
const baseOptions: Options =
19+
process.env.NODE_ENV === 'production'
20+
? {
21+
apiBaseUrl: 'https://raincloud.fm',
22+
clientId: 'ttmRWSTGJ7pzm1s8znU3CSJ5mXSjtS0l',
23+
redirectUri: 'https://raincloud.fm/oauth',
24+
}
25+
: {
26+
apiBaseUrl: 'http://localhost:3001',
27+
clientId: 'Gbv3N4cjTjVMwfaHVbCdEB7W5Y3JQM28',
28+
redirectUri: 'http://localhost:3000',
29+
};
30+
1531
this.options = {
16-
apiBaseUrl: 'https://api.raincloud.fm',
17-
clientId:
18-
process.env.NODE_ENV === 'production'
19-
? 'ttmRWSTGJ7pzm1s8znU3CSJ5mXSjtS0l'
20-
: 'Gbv3N4cjTjVMwfaHVbCdEB7W5Y3JQM28',
32+
...baseOptions,
2133
...options,
2234
};
2335
}
@@ -34,10 +46,8 @@ export class Auth {
3446
throw new Error('No session found! You must sign in first.');
3547
}
3648

37-
if (session.expires_at < Date.now() + 600000) {
38-
console.log(
39-
`Session expiring (${new Date(session.expires_at).toISOString()}), refreshing...`
40-
);
49+
if (session.expiresAt < Date.now() + 600000) {
50+
console.log(`Session expiring (${new Date(session.expiresAt).toISOString()}), refreshing...`);
4151
await this.refreshSession(session);
4252
session = JSON.parse(window.localStorage.getItem(AUTH_STORAGE_KEY) as string);
4353
}
@@ -59,13 +69,35 @@ export class Auth {
5969
window.localStorage.removeItem(AUTH_STORAGE_KEY);
6070
}
6171

72+
async initiateWebLogin(): Promise<void> {
73+
const url = new URL('https://secure.soundcloud.com/authorize');
74+
url.searchParams.append('response_type', 'code');
75+
url.searchParams.append('client_id', this.options.clientId);
76+
url.searchParams.append('redirect_uri', this.options.redirectUri);
77+
78+
const codeVerifier = randomPKCECodeVerifier();
79+
sessionStorage.setItem(CODE_VERIFIER_KEY, codeVerifier);
80+
const codeChalenge = await calculatePKCECodeChallenge(codeVerifier);
81+
url.searchParams.append('code_challenge', codeChalenge);
82+
url.searchParams.append('code_challenge_method', 'S256');
83+
84+
const state = randomState();
85+
url.searchParams.append('state', state);
86+
87+
console.log('url', url.toString());
88+
89+
window.location.href = url.toString();
90+
}
91+
6292
async fetchTokensFromQRCode(): Promise<void> {
6393
const qrCode = new KaiOS.QRCode();
6494
const text = await qrCode.readAsText();
65-
const user: User = await this.fetchAuthUser(JSON.parse(text).access_token);
95+
const payload = JSON.parse(text) as Tokens;
96+
97+
const user: User = await this.fetchAuthUser(payload.accessToken);
6698

6799
this.setSession({
68-
...JSON.parse(text),
100+
...payload,
69101
user_id: user.id,
70102
user_name: user.full_name || user.username,
71103
user_avatar_url: user.avatar_url,
@@ -82,16 +114,18 @@ export class Auth {
82114
resolve(JSON.parse(xhr.responseText));
83115
});
84116
xhr.addEventListener('error', () => reject(new Error('Failed to fetch tokens')));
85-
xhr.open('POST', `${this.options.apiBaseUrl}/getTokens`);
117+
xhr.open('POST', `${this.options.apiBaseUrl}/api/v1/token`);
86118
xhr.setRequestHeader('Content-Type', 'application/json');
87119
xhr.send(
88120
JSON.stringify({
89121
clientId: this.options.clientId,
90122
code,
123+
codeVerifier: sessionStorage.getItem(CODE_VERIFIER_KEY),
124+
redirectUri: this.options.redirectUri,
91125
})
92126
);
93127
});
94-
const user: User = await this.fetchAuthUser(tokens.access_token);
128+
const user: User = await this.fetchAuthUser(tokens.accessToken);
95129

96130
this.setSession({
97131
...tokens,
@@ -134,12 +168,12 @@ export class Auth {
134168
resolve(JSON.parse(xhr.responseText));
135169
});
136170
xhr.addEventListener('error', () => reject(new Error('Failed to refresh tokens')));
137-
xhr.open('POST', `${this.options.apiBaseUrl}/refreshTokens`);
171+
xhr.open('POST', `${this.options.apiBaseUrl}/api/v1/token/refresh`);
138172
xhr.setRequestHeader('Content-Type', 'application/json');
139173
xhr.send(
140174
JSON.stringify({
141175
clientId: this.options.clientId,
142-
refreshToken: session.refresh_token,
176+
refreshToken: session.refreshToken,
143177
})
144178
);
145179
})

0 commit comments

Comments
 (0)