replace node-fetch with wretch

This commit is contained in:
Puyodead1 2023-01-13 08:52:24 -05:00
parent 06140fc768
commit d8ecc4269f
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
13 changed files with 6032 additions and 549 deletions

9
package-lock.json generated
View File

@ -50,6 +50,7 @@
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typeorm": "^0.3.10", "typeorm": "^0.3.10",
"typescript-json-schema": "^0.50.1", "typescript-json-schema": "^0.50.1",
"wretch": "^2.3.2",
"ws": "^8.9.0" "ws": "^8.9.0"
}, },
"devDependencies": { "devDependencies": {
@ -7988,6 +7989,14 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/wretch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/wretch/-/wretch-2.3.2.tgz",
"integrity": "sha512-brN97Z2Mwed+w5z+keYI1u5OwWhPIaW0sJi9CxtKBVxLc3aqP6j1+2FCoIskM7WJq6SUHdxTFx20ox0iDLa0mQ==",
"engines": {
"node": ">=14"
}
},
"node_modules/ws": { "node_modules/ws": {
"version": "8.11.0", "version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",

View File

@ -105,6 +105,7 @@
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typeorm": "^0.3.10", "typeorm": "^0.3.10",
"typescript-json-schema": "^0.50.1", "typescript-json-schema": "^0.50.1",
"wretch": "^2.3.2",
"ws": "^8.9.0" "ws": "^8.9.0"
}, },
"_moduleAliases": { "_moduleAliases": {

5745
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { BattleNetSettings } from "./BattleNetSettings"; import { BattleNetSettings } from "./BattleNetSettings";
@ -67,68 +66,40 @@ export default class BattleNetConnection extends Connection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
client_secret: this.settings.clientSecret!, client_secret: this.settings.clientSecret!,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
}), }),
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
BattleNetErrorResponse,
) => {
if (res.error) throw new Error(res.error_description);
return res;
},
) )
.post()
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<BattleNetConnectionUser> { async getUser(token: string): Promise<BattleNetConnectionUser> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
})
.then((res: BattleNetConnectionUser & BattleNetErrorResponse) => {
if (res.error) throw new Error(res.error_description);
return res;
}) })
.get()
.json<BattleNetConnectionUser>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { DiscordSettings } from "./DiscordSettings"; import { DiscordSettings } from "./DiscordSettings";
@ -66,56 +65,41 @@ export default class DiscordConnection extends Connection {
this.validateState(state); this.validateState(state);
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url, { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
client_secret: this.settings.clientSecret!, client_secret: this.settings.clientSecret!,
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json<ConnectedAccountCommonOAuthTokenResponse>()
throw new ApiError("Failed to exchange token", 0, 400);
}
return res.json();
})
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<UserResponse> { async getUser(token: string): Promise<UserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
}) })
.get()
.json<UserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { EpicGamesSettings } from "./EpicGamesSettings"; import { EpicGamesSettings } from "./EpicGamesSettings";
@ -73,31 +72,24 @@ export default class EpicGamesConnection extends Connection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId}:${this.settings.clientSecret}`, `${this.settings.clientId}:${this.settings.clientSecret}`,
).toString("base64")}`, ).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code, code,
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json<EpicTokenResponse>()
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
@ -108,23 +100,15 @@ export default class EpicGamesConnection extends Connection {
); );
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
url.searchParams.append("accountId", sub); url.searchParams.append("accountId", sub);
return fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json(); return wretch(url.toString())
.headers({
Authorization: `Bearer ${token}`,
}) })
.get()
.json<UserResponse[]>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { FacebookSettings } from "./FacebookSettings"; import { FacebookSettings } from "./FacebookSettings";
@ -83,59 +82,29 @@ export default class FacebookConnection extends Connection {
const url = this.getTokenUrl(code); const url = this.getTokenUrl(code);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
},
}) })
.then((res) => { .get()
if (!res.ok) { .json<ConnectedAccountCommonOAuthTokenResponse>()
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
FacebookErrorResponse,
) => {
if (res.error) throw new Error(res.error.message);
return res;
},
)
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<UserResponse> { async getUser(token: string): Promise<UserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json(); return wretch(url.toString())
}) .headers({
.then((res: UserResponse & FacebookErrorResponse) => { Authorization: `Bearer ${token}`,
if (res.error) throw new Error(res.error.message);
return res;
}) })
.get()
.json<UserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { GitHubSettings } from "./GitHubSettings"; import { GitHubSettings } from "./GitHubSettings";
@ -65,46 +64,29 @@ export default class GitHubConnection extends Connection {
const url = this.getTokenUrl(code); const url = this.getTokenUrl(code);
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
},
}) })
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json(); .post()
}) .json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<UserResponse> { async getUser(token: string): Promise<UserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
}) })
.get()
.json<UserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { RedditSettings } from "./RedditSettings"; import { RedditSettings } from "./RedditSettings";
@ -74,57 +73,42 @@ export default class RedditConnection extends Connection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId}:${this.settings.clientSecret}`, `${this.settings.clientId}:${this.settings.clientSecret}`,
).toString("base64")}`, ).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json<ConnectedAccountCommonOAuthTokenResponse>()
throw new ApiError("Failed to code", 0, 400);
}
return res.json();
})
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<UserResponse> { async getUser(token: string): Promise<UserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
}) })
.get()
.json<UserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import RefreshableConnection from "../../util/connections/RefreshableConnection"; import RefreshableConnection from "../../util/connections/RefreshableConnection";
import { SpotifySettings } from "./SpotifySettings"; import { SpotifySettings } from "./SpotifySettings";
@ -83,122 +82,78 @@ export default class SpotifyConnection extends RefreshableConnection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId!}:${this.settings.clientSecret!}`, `${this.settings.clientId!}:${this.settings.clientSecret!}`,
).toString("base64")}`, ).toString("base64")}`,
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
}), }),
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to refresh token", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
TokenErrorResponse,
) => {
if (res.error)
throw new ApiError(res.error_description, 0, 400);
return res;
},
) )
.post()
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async refreshToken(connectedAccount: ConnectedAccount) { async refreshToken(
connectedAccount: ConnectedAccount,
): Promise<ConnectedAccountCommonOAuthTokenResponse> {
if (!connectedAccount.token_data?.refresh_token) if (!connectedAccount.token_data?.refresh_token)
throw new Error("No refresh token available."); throw new Error("No refresh token available.");
const refresh_token = connectedAccount.token_data.refresh_token; const refresh_token = connectedAccount.token_data.refresh_token;
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId!}:${this.settings.clientSecret!}`, `${this.settings.clientId!}:${this.settings.clientSecret!}`,
).toString("base64")}`, ).toString("base64")}`,
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "refresh_token", grant_type: "refresh_token",
refresh_token, refresh_token,
}), }),
}) )
.then(async (res) => { .post()
if ([400, 401].includes(res.status)) { .unauthorized(async () => {
// assume the token was revoked // assume the token was revoked
await connectedAccount.revoke(); await connectedAccount.revoke();
return DiscordApiErrors.CONNECTION_REVOKED; return DiscordApiErrors.CONNECTION_REVOKED;
}
// otherwise throw a general error
if (!res.ok) {
throw new ApiError("Failed to refresh token", 0, 400);
}
return await res.json();
}) })
.then( .json<ConnectedAccountCommonOAuthTokenResponse>()
(
res: ConnectedAccountCommonOAuthTokenResponse &
TokenErrorResponse,
) => {
if (res.error)
throw new ApiError(res.error_description, 0, 400);
return res;
},
)
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error refreshing token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<UserResponse> { async getUser(token: string): Promise<UserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json(); return wretch(url.toString())
}) .headers({
.then((res: UserResponse & ErrorResponse) => { Authorization: `Bearer ${token}`,
if (res.error) throw new Error(res.error.message);
return res;
}) })
.get()
.json<UserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import RefreshableConnection from "../../util/connections/RefreshableConnection"; import RefreshableConnection from "../../util/connections/RefreshableConnection";
import { TwitchSettings } from "./TwitchSettings"; import { TwitchSettings } from "./TwitchSettings";
@ -75,33 +74,27 @@ export default class TwitchConnection extends RefreshableConnection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
client_secret: this.settings.clientSecret!, client_secret: this.settings.clientSecret!,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json<ConnectedAccountCommonOAuthTokenResponse>()
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
@ -115,60 +108,44 @@ export default class TwitchConnection extends RefreshableConnection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "refresh_token", grant_type: "refresh_token",
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
client_secret: this.settings.clientSecret!, client_secret: this.settings.clientSecret!,
refresh_token: refresh_token, refresh_token: refresh_token,
}), }),
}) )
.then(async (res) => { .post()
if ([400, 401].includes(res.status)) { .unauthorized(async () => {
// assume the token was revoked // assume the token was revoked
await connectedAccount.revoke(); await connectedAccount.revoke();
return DiscordApiErrors.CONNECTION_REVOKED; return DiscordApiErrors.CONNECTION_REVOKED;
}
// otherwise throw a general error
if (!res.ok) {
throw new ApiError("Failed to refresh token", 0, 400);
}
return await res.json();
}) })
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error refreshing token for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<TwitchConnectionUserResponse> { async getUser(token: string): Promise<TwitchConnectionUserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), {
method: "GET", return wretch(url.toString())
headers: { .headers({
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
"Client-Id": this.settings.clientId!, "Client-Id": this.settings.clientId!,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
}) })
.get()
.json<TwitchConnectionUserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import RefreshableConnection from "../../util/connections/RefreshableConnection"; import RefreshableConnection from "../../util/connections/RefreshableConnection";
import { TwitterSettings } from "./TwitterSettings"; import { TwitterSettings } from "./TwitterSettings";
@ -77,45 +76,30 @@ export default class TwitterConnection extends RefreshableConnection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId!}:${this.settings.clientSecret!}`, `${this.settings.clientId!}:${this.settings.clientSecret!}`,
).toString("base64")}`, ).toString("base64")}`,
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
code_verifier: "challenge", // TODO: properly use PKCE challenge code_verifier: "challenge", // TODO: properly use PKCE challenge
}), }),
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
TwitterErrorResponse,
) => {
if (res.error) throw new Error(res.error_description);
return res;
},
) )
.post()
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
@ -129,72 +113,44 @@ export default class TwitterConnection extends RefreshableConnection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId!}:${this.settings.clientSecret!}`, `${this.settings.clientId!}:${this.settings.clientSecret!}`,
).toString("base64")}`, ).toString("base64")}`,
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "refresh_token", grant_type: "refresh_token",
refresh_token, refresh_token,
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
code_verifier: "challenge", // TODO: properly use PKCE challenge code_verifier: "challenge", // TODO: properly use PKCE challenge
}), }),
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
TwitterErrorResponse,
) => {
if (res.error) throw new Error(res.error_description);
return res;
},
) )
.post()
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<TwitterUserResponse> { async getUser(token: string): Promise<TwitterUserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), { return wretch(url.toString())
method: "GET", .headers({
headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
},
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
})
.then((res: TwitterUserResponse & TwitterErrorResponse) => {
if (res.error) throw new Error(res.error_description);
return res;
}) })
.get()
.json<TwitterUserResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }

View File

@ -1,5 +1,4 @@
import { import {
ApiError,
Config, Config,
ConnectedAccount, ConnectedAccount,
ConnectedAccountCommonOAuthTokenResponse, ConnectedAccountCommonOAuthTokenResponse,
@ -7,7 +6,7 @@ import {
ConnectionLoader, ConnectionLoader,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import fetch from "node-fetch"; import wretch from "wretch";
import Connection from "../../util/connections/Connection"; import Connection from "../../util/connections/Connection";
import { XboxSettings } from "./XboxSettings"; import { XboxSettings } from "./XboxSettings";
@ -76,14 +75,14 @@ export default class XboxConnection extends Connection {
} }
async getUserToken(token: string): Promise<string> { async getUserToken(token: string): Promise<string> {
return fetch(this.userAuthUrl, { return wretch(this.userAuthUrl)
method: "POST", .headers({
headers: {
"x-xbl-contract-version": "3", "x-xbl-contract-version": "3",
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, })
body: JSON.stringify({ .body(
JSON.stringify({
RelyingParty: "http://auth.xboxlive.com", RelyingParty: "http://auth.xboxlive.com",
TokenType: "JWT", TokenType: "JWT",
Properties: { Properties: {
@ -92,20 +91,12 @@ export default class XboxConnection extends Connection {
RpsTicket: `d=${token}`, RpsTicket: `d=${token}`,
}, },
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json((res: XboxUserResponse) => res.Token)
throw new ApiError("Failed to get user token", 0, 400);
}
return res.json();
})
.then((res) => res.Token)
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error getting user token for ${this.id} connection: ${e}`, throw DiscordApiErrors.GENERAL_ERROR;
);
throw DiscordApiErrors.INVALID_OAUTH_TOKEN;
}); });
} }
@ -117,59 +108,45 @@ export default class XboxConnection extends Connection {
const url = this.getTokenUrl(); const url = this.getTokenUrl();
return fetch(url.toString(), { return wretch(url.toString())
method: "POST", .headers({
headers: {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from( Authorization: `Basic ${Buffer.from(
`${this.settings.clientId!}:${this.settings.clientSecret!}`, `${this.settings.clientId!}:${this.settings.clientSecret!}`,
).toString("base64")}`, ).toString("base64")}`,
}, })
body: new URLSearchParams({ .body(
new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
code: code, code: code,
client_id: this.settings.clientId!, client_id: this.settings.clientId!,
redirect_uri: `${ redirect_uri: `${
Config.get().cdn.endpointPrivate || "http://localhost:3001" Config.get().cdn.endpointPrivate ||
"http://localhost:3001"
}/connections/${this.id}/callback`, }/connections/${this.id}/callback`,
scope: this.scopes.join(" "), scope: this.scopes.join(" "),
}), }),
})
.then((res) => {
if (!res.ok) {
throw new ApiError("Failed to exchange code", 0, 400);
}
return res.json();
})
.then(
(
res: ConnectedAccountCommonOAuthTokenResponse &
XboxErrorResponse,
) => {
if (res.error) throw new Error(res.error_description);
return res;
},
) )
.post()
.json<ConnectedAccountCommonOAuthTokenResponse>()
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error exchanging code for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }
async getUser(token: string): Promise<XboxUserResponse> { async getUser(token: string): Promise<XboxUserResponse> {
const url = new URL(this.userInfoUrl); const url = new URL(this.userInfoUrl);
return fetch(url.toString(), {
method: "POST", return wretch(url.toString())
headers: { .headers({
"x-xbl-contract-version": "3", "x-xbl-contract-version": "3",
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, })
body: JSON.stringify({ .body(
JSON.stringify({
RelyingParty: "http://xboxlive.com", RelyingParty: "http://xboxlive.com",
TokenType: "JWT", TokenType: "JWT",
Properties: { Properties: {
@ -177,22 +154,11 @@ export default class XboxConnection extends Connection {
SandboxId: "RETAIL", SandboxId: "RETAIL",
}, },
}), }),
}) )
.then((res) => { .post()
if (!res.ok) { .json<XboxUserResponse>()
throw new ApiError("Failed to fetch user", 0, 400);
}
return res.json();
})
.then((res: XboxUserResponse & XboxErrorResponse) => {
if (res.error) throw new Error(res.error_description);
return res;
})
.catch((e) => { .catch((e) => {
console.error( console.error(e);
`Error fetching user for ${this.id} connection: ${e}`,
);
throw DiscordApiErrors.GENERAL_ERROR; throw DiscordApiErrors.GENERAL_ERROR;
}); });
} }