e621 3.4.1 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/browser/e621.js +1191 -252
- package/dist/browser/e621.js.map +4 -4
- package/dist/lib/OAuthClient.d.ts +152 -0
- package/dist/lib/OAuthClient.js +223 -0
- package/dist/lib/client.d.ts +75 -1
- package/dist/lib/client.js +61 -29
- package/dist/lib/errors.d.ts +26 -0
- package/dist/lib/errors.js +50 -2
- package/dist/lib/generated/schemas.d.ts +169 -8
- package/dist/lib/generated/schemas.js +215 -13
- package/dist/lib/generated/sdk.d.ts +77 -7
- package/dist/lib/generated/sdk.js +134 -9
- package/dist/lib/generated/types.d.ts +880 -245
- package/dist/lib/index.d.ts +3 -1
- package/dist/lib/index.js +2 -1
- package/dist/lib/models/Artist.d.ts +1 -0
- package/dist/lib/models/Artist.js +2 -1
- package/dist/lib/models/SavedSearch.d.ts +37 -0
- package/dist/lib/models/SavedSearch.js +55 -0
- package/dist/lib/models/TagSubscription.d.ts +25 -0
- package/dist/lib/models/TagSubscription.js +36 -0
- package/dist/lib/models/Takedown.d.ts +13 -2
- package/dist/lib/models/Takedown.js +21 -2
- package/dist/lib/models/User.d.ts +1 -1
- package/dist/lib/models/UserNameChangeRequest.d.ts +1 -1
- package/dist/lib/models/index.d.ts +2 -0
- package/dist/lib/models/index.js +3 -1
- package/dist/lib/models/posts/Actions.d.ts +6 -6
- package/dist/lib/modules/Artists.d.ts +2 -0
- package/dist/lib/modules/Artists.js +3 -1
- package/dist/lib/modules/Base.js +4 -2
- package/dist/lib/modules/DText.d.ts +2 -3
- package/dist/lib/modules/DText.js +5 -6
- package/dist/lib/modules/PostVersions.d.ts +2 -1
- package/dist/lib/modules/PostVersions.js +3 -2
- package/dist/lib/modules/Posts.d.ts +6 -6
- package/dist/lib/modules/Posts.js +8 -14
- package/dist/lib/modules/SavedSearches.d.ts +94 -0
- package/dist/lib/modules/SavedSearches.js +162 -0
- package/dist/lib/modules/SearchTrendBlacklists.js +1 -1
- package/dist/lib/modules/StaffUsers.d.ts +1 -1
- package/dist/lib/modules/StaffUsers.js +4 -2
- package/dist/lib/modules/TagAliases.d.ts +1 -1
- package/dist/lib/modules/TagAliases.js +3 -2
- package/dist/lib/modules/TagImplications.d.ts +1 -1
- package/dist/lib/modules/TagImplications.js +3 -2
- package/dist/lib/modules/TagSubscriptions.d.ts +49 -0
- package/dist/lib/modules/TagSubscriptions.js +82 -0
- package/dist/lib/modules/Takedowns.d.ts +16 -2
- package/dist/lib/modules/Takedowns.js +25 -3
- package/dist/lib/modules/Uploads.d.ts +29 -2
- package/dist/lib/modules/Uploads.js +47 -11
- package/dist/lib/modules/UserNameChangeRequests.d.ts +2 -2
- package/dist/lib/modules/UserNameChangeRequests.js +5 -3
- package/dist/lib/modules/index.d.ts +6 -0
- package/dist/lib/modules/index.js +5 -1
- package/dist/lib/standalone.d.ts +1 -1
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type Options } from "./client.js";
|
|
2
|
+
import type { OAuthIntrospection, OAuthScope, OAuthToken, OAuthTokenInfo, OAuthUserInfo, OpenIDConfiguration } from "./generated/types.js";
|
|
3
|
+
/** @category OAuth */
|
|
4
|
+
export interface OAuthClientOptions extends Pick<Options, "baseURL" | "ratelimit" | "requestTimeout" | "userAgent" | "userAgentInQuery"> {
|
|
5
|
+
/** The client id (uid) of your OAuth application. */
|
|
6
|
+
clientId: string;
|
|
7
|
+
/**
|
|
8
|
+
* The client secret of your OAuth application. Leave out for public clients (e.g. browser or
|
|
9
|
+
* desktop apps, where the secret can't be kept private) - PKCE is always used either way.
|
|
10
|
+
* @default none
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Called whenever the stored token changes (exchanged, refreshed, or revoked - `null` in that case).
|
|
15
|
+
* Use this to persist the token, and pass it back in as {@link OAuthClientOptions.token} later.
|
|
16
|
+
* @default none
|
|
17
|
+
*/
|
|
18
|
+
onToken?: (token: OAuthToken | null) => void | Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* The redirect uri registered for your application. Used by default by
|
|
21
|
+
* {@link OAuthClient.createAuthorizationURL} and {@link OAuthClient.exchangeCode}.
|
|
22
|
+
* @default none
|
|
23
|
+
*/
|
|
24
|
+
redirectURI?: string;
|
|
25
|
+
/**
|
|
26
|
+
* How many seconds before the access token expires that {@link OAuthClient.getAccessToken} should
|
|
27
|
+
* refresh it.
|
|
28
|
+
* @default 60
|
|
29
|
+
*/
|
|
30
|
+
refreshLeeway?: number;
|
|
31
|
+
/**
|
|
32
|
+
* The scopes to request by default in {@link OAuthClient.createAuthorizationURL}.
|
|
33
|
+
* @default none (the application's default scopes)
|
|
34
|
+
*/
|
|
35
|
+
scopes?: Array<OAuthScope>;
|
|
36
|
+
/**
|
|
37
|
+
* A previously obtained token to restore, e.g. one saved from {@link OAuthClientOptions.onToken}.
|
|
38
|
+
* @default none
|
|
39
|
+
*/
|
|
40
|
+
token?: OAuthToken | null;
|
|
41
|
+
}
|
|
42
|
+
/** @category OAuth */
|
|
43
|
+
export interface AuthorizationURLOptions {
|
|
44
|
+
/** Override the code verifier, a random one is generated by default. */
|
|
45
|
+
codeVerifier?: string;
|
|
46
|
+
/** Sent as the OpenID Connect `nonce`, included in the returned ID token. */
|
|
47
|
+
nonce?: string;
|
|
48
|
+
prompt?: "none" | "login" | "consent" | "select_account";
|
|
49
|
+
/** @default {@link OAuthClientOptions.redirectURI} */
|
|
50
|
+
redirectURI?: string;
|
|
51
|
+
/** @default {@link OAuthClientOptions.scopes} */
|
|
52
|
+
scopes?: Array<OAuthScope>;
|
|
53
|
+
/** Override the state, a random one is generated by default. */
|
|
54
|
+
state?: string;
|
|
55
|
+
}
|
|
56
|
+
/** @category OAuth */
|
|
57
|
+
export interface AuthorizationRequest {
|
|
58
|
+
/** Must be kept (e.g. in the session) and passed to {@link OAuthClient.exchangeCode} or {@link OAuthClient.handleCallback}. */
|
|
59
|
+
codeVerifier: string;
|
|
60
|
+
redirectURI: string | null;
|
|
61
|
+
/** Must be kept and checked against the callback's state - {@link OAuthClient.handleCallback} does this for you. */
|
|
62
|
+
state: string;
|
|
63
|
+
/** The url to send the user to. */
|
|
64
|
+
url: string;
|
|
65
|
+
}
|
|
66
|
+
/** @category OAuth */
|
|
67
|
+
export interface HandleCallbackOptions {
|
|
68
|
+
codeVerifier: string;
|
|
69
|
+
/** @default {@link OAuthClientOptions.redirectURI} */
|
|
70
|
+
redirectURI?: string;
|
|
71
|
+
/** The state from {@link AuthorizationRequest.state}, compared against the one in the callback. */
|
|
72
|
+
state: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A helper for e621's OAuth 2.0 / OpenID Connect flow (authorization code + PKCE). It builds authorization
|
|
76
|
+
* urls, exchanges codes, and keeps the token refreshed. Pass {@link OAuthClient.getAccessToken} as
|
|
77
|
+
* {@link Options.authToken} to make requests as the authorized user:
|
|
78
|
+
* ```ts
|
|
79
|
+
* const oauth = new OAuthClient({ clientId: "...", redirectURI: "https://example.com/callback", scopes: ["openid", "full"] });
|
|
80
|
+
* const { url, state, codeVerifier } = await oauth.createAuthorizationURL();
|
|
81
|
+
* // send the user to `url`, then once they're redirected back:
|
|
82
|
+
* await oauth.handleCallback(callbackURL, { state, codeVerifier });
|
|
83
|
+
* const e621 = new E621({ authToken: oauth.getAccessToken });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* OAuth 2.0 / OpenID Connect support is present in the spec but has not been publicly released on e621
|
|
87
|
+
* yet - these endpoints are not yet available for use.
|
|
88
|
+
*
|
|
89
|
+
* @category OAuth
|
|
90
|
+
*/
|
|
91
|
+
export default class OAuthClient {
|
|
92
|
+
private _clientSecret;
|
|
93
|
+
private _discovery;
|
|
94
|
+
private _onToken;
|
|
95
|
+
private _refreshing;
|
|
96
|
+
private _token;
|
|
97
|
+
private _tokenInfo;
|
|
98
|
+
private _tokens;
|
|
99
|
+
private _userinfo;
|
|
100
|
+
readonly baseURL: string;
|
|
101
|
+
readonly clientId: string;
|
|
102
|
+
readonly redirectURI: string | null;
|
|
103
|
+
readonly refreshLeeway: number;
|
|
104
|
+
readonly scopes: Array<OAuthScope>;
|
|
105
|
+
constructor(options: OAuthClientOptions);
|
|
106
|
+
/** Creates a PKCE `code_challenge` (S256) from a code verifier. */
|
|
107
|
+
static createCodeChallenge(codeVerifier: string): Promise<string>;
|
|
108
|
+
/** Creates a random PKCE code verifier. */
|
|
109
|
+
static createCodeVerifier(): string;
|
|
110
|
+
/** Creates a random state value. */
|
|
111
|
+
static createState(): string;
|
|
112
|
+
/** When the current access token expires, as a unix timestamp in milliseconds - `null` if there's no token. */
|
|
113
|
+
get expiresAt(): number | null;
|
|
114
|
+
/** Whether the current access token is expired (or within {@link OAuthClientOptions.refreshLeeway} of expiring). */
|
|
115
|
+
get isExpired(): boolean;
|
|
116
|
+
/** The current token, if any. */
|
|
117
|
+
get token(): OAuthToken | null;
|
|
118
|
+
private _clientAuth;
|
|
119
|
+
private _requireToken;
|
|
120
|
+
/** Builds the url to send the user to, to authorize your application. */
|
|
121
|
+
createAuthorizationURL(options?: AuthorizationURLOptions): Promise<AuthorizationRequest>;
|
|
122
|
+
/** Gets e621's OpenID Connect discovery document. */
|
|
123
|
+
discovery(): Promise<OpenIDConfiguration>;
|
|
124
|
+
/** Exchanges an authorization code for a token, and stores it. */
|
|
125
|
+
exchangeCode(code: string, codeVerifier: string, redirectURI?: string | null): Promise<OAuthToken>;
|
|
126
|
+
/**
|
|
127
|
+
* Gets a valid access token, refreshing it first if it's expired. Already bound, so it can be passed
|
|
128
|
+
* directly as {@link Options.authToken}.
|
|
129
|
+
*/
|
|
130
|
+
getAccessToken(): Promise<string>;
|
|
131
|
+
/**
|
|
132
|
+
* Validates the url the user was redirected back to (state, and any error e621 sent), then exchanges
|
|
133
|
+
* its code for a token.
|
|
134
|
+
* @param callbackURL - The full callback url, or just its query string.
|
|
135
|
+
*/
|
|
136
|
+
handleCallback(callbackURL: string | URL | URLSearchParams, options: HandleCallbackOptions): Promise<OAuthToken>;
|
|
137
|
+
/** Introspects a token - by default the current access token. */
|
|
138
|
+
introspect(token?: string): Promise<OAuthIntrospection>;
|
|
139
|
+
/**
|
|
140
|
+
* Refreshes the access token, and stores the new token. Concurrent calls share the same request.
|
|
141
|
+
* @param scopes - Narrow the new token to these scopes.
|
|
142
|
+
*/
|
|
143
|
+
refresh(scopes?: Array<OAuthScope>): Promise<OAuthToken>;
|
|
144
|
+
/** Revokes the current token (which also invalidates its refresh token), and clears it. */
|
|
145
|
+
revoke(): Promise<void>;
|
|
146
|
+
/** Replaces the stored token, calling {@link OAuthClientOptions.onToken}. */
|
|
147
|
+
setToken(token: OAuthToken | null): Promise<void>;
|
|
148
|
+
/** Gets metadata about the current access token. */
|
|
149
|
+
tokenInfo(): Promise<OAuthTokenInfo>;
|
|
150
|
+
/** Gets the OpenID Connect claims of the authorized user, gated by the token's scopes. */
|
|
151
|
+
userInfo(): Promise<OAuthUserInfo>;
|
|
152
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createE621Client } from "./client.js";
|
|
2
|
+
import { OAuthCallbackError } from "./errors.js";
|
|
3
|
+
import DoorkeeperOpenidConnectDiscovery from "./modules/DoorkeeperOpenidConnectDiscovery.js";
|
|
4
|
+
import DoorkeeperOpenidConnectUserinfo from "./modules/DoorkeeperOpenidConnectUserinfo.js";
|
|
5
|
+
import DoorkeeperTokenInfo from "./modules/DoorkeeperTokenInfo.js";
|
|
6
|
+
import DoorkeeperTokens from "./modules/DoorkeeperTokens.js";
|
|
7
|
+
function base64URL(bytes) {
|
|
8
|
+
let str = "";
|
|
9
|
+
for (const byte of bytes)
|
|
10
|
+
str += String.fromCharCode(byte);
|
|
11
|
+
return btoa(str).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
12
|
+
}
|
|
13
|
+
function randomString(byteLength = 32) {
|
|
14
|
+
return base64URL(crypto.getRandomValues(new Uint8Array(byteLength)));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A helper for e621's OAuth 2.0 / OpenID Connect flow (authorization code + PKCE). It builds authorization
|
|
18
|
+
* urls, exchanges codes, and keeps the token refreshed. Pass {@link OAuthClient.getAccessToken} as
|
|
19
|
+
* {@link Options.authToken} to make requests as the authorized user:
|
|
20
|
+
* ```ts
|
|
21
|
+
* const oauth = new OAuthClient({ clientId: "...", redirectURI: "https://example.com/callback", scopes: ["openid", "full"] });
|
|
22
|
+
* const { url, state, codeVerifier } = await oauth.createAuthorizationURL();
|
|
23
|
+
* // send the user to `url`, then once they're redirected back:
|
|
24
|
+
* await oauth.handleCallback(callbackURL, { state, codeVerifier });
|
|
25
|
+
* const e621 = new E621({ authToken: oauth.getAccessToken });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* OAuth 2.0 / OpenID Connect support is present in the spec but has not been publicly released on e621
|
|
29
|
+
* yet - these endpoints are not yet available for use.
|
|
30
|
+
*
|
|
31
|
+
* @category OAuth
|
|
32
|
+
*/
|
|
33
|
+
export default class OAuthClient {
|
|
34
|
+
_clientSecret;
|
|
35
|
+
_discovery;
|
|
36
|
+
_onToken;
|
|
37
|
+
_refreshing;
|
|
38
|
+
_token;
|
|
39
|
+
_tokenInfo;
|
|
40
|
+
_tokens;
|
|
41
|
+
_userinfo;
|
|
42
|
+
baseURL;
|
|
43
|
+
clientId;
|
|
44
|
+
redirectURI;
|
|
45
|
+
refreshLeeway;
|
|
46
|
+
scopes;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
const { clientId, clientSecret, onToken, redirectURI, refreshLeeway, scopes, token, ...clientOptions } = options;
|
|
49
|
+
this.clientId = clientId;
|
|
50
|
+
this.redirectURI = redirectURI ?? null;
|
|
51
|
+
this.refreshLeeway = refreshLeeway ?? 60;
|
|
52
|
+
this.scopes = scopes ?? [];
|
|
53
|
+
// the token endpoints authenticate the application, never the user, so they get a client without user auth
|
|
54
|
+
const { client, options: resolved } = createE621Client(clientOptions);
|
|
55
|
+
const { client: authedClient } = createE621Client({ ...clientOptions, authToken: () => this.getAccessToken() });
|
|
56
|
+
this.baseURL = resolved.baseURL;
|
|
57
|
+
Object.defineProperties(this, {
|
|
58
|
+
_clientSecret: { value: clientSecret ?? null, enumerable: false },
|
|
59
|
+
_discovery: { value: new DoorkeeperOpenidConnectDiscovery(undefined, client), enumerable: false },
|
|
60
|
+
_onToken: { value: onToken, enumerable: false },
|
|
61
|
+
_refreshing: { value: null, writable: true, enumerable: false },
|
|
62
|
+
_token: { value: token ?? null, writable: true, enumerable: false },
|
|
63
|
+
_tokenInfo: { value: new DoorkeeperTokenInfo(undefined, authedClient), enumerable: false },
|
|
64
|
+
_tokens: { value: new DoorkeeperTokens(undefined, client), enumerable: false },
|
|
65
|
+
_userinfo: { value: new DoorkeeperOpenidConnectUserinfo(undefined, authedClient), enumerable: false },
|
|
66
|
+
});
|
|
67
|
+
this.getAccessToken = this.getAccessToken.bind(this);
|
|
68
|
+
}
|
|
69
|
+
/** Creates a PKCE `code_challenge` (S256) from a code verifier. */
|
|
70
|
+
static async createCodeChallenge(codeVerifier) {
|
|
71
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
|
|
72
|
+
return base64URL(new Uint8Array(digest));
|
|
73
|
+
}
|
|
74
|
+
/** Creates a random PKCE code verifier. */
|
|
75
|
+
static createCodeVerifier() {
|
|
76
|
+
return randomString(32);
|
|
77
|
+
}
|
|
78
|
+
/** Creates a random state value. */
|
|
79
|
+
static createState() {
|
|
80
|
+
return randomString(16);
|
|
81
|
+
}
|
|
82
|
+
/** When the current access token expires, as a unix timestamp in milliseconds - `null` if there's no token. */
|
|
83
|
+
get expiresAt() {
|
|
84
|
+
if (this._token === null)
|
|
85
|
+
return null;
|
|
86
|
+
return (this._token.created_at + this._token.expires_in) * 1000;
|
|
87
|
+
}
|
|
88
|
+
/** Whether the current access token is expired (or within {@link OAuthClientOptions.refreshLeeway} of expiring). */
|
|
89
|
+
get isExpired() {
|
|
90
|
+
const expiresAt = this.expiresAt;
|
|
91
|
+
return expiresAt === null || Date.now() >= expiresAt - this.refreshLeeway * 1000;
|
|
92
|
+
}
|
|
93
|
+
/** The current token, if any. */
|
|
94
|
+
get token() {
|
|
95
|
+
return this._token;
|
|
96
|
+
}
|
|
97
|
+
_clientAuth() {
|
|
98
|
+
return this._clientSecret === null ? { client_id: this.clientId } : { client_id: this.clientId, client_secret: this._clientSecret };
|
|
99
|
+
}
|
|
100
|
+
_requireToken() {
|
|
101
|
+
if (this._token === null)
|
|
102
|
+
throw new Error("No OAuth token is available, the user must authorize first.");
|
|
103
|
+
return this._token;
|
|
104
|
+
}
|
|
105
|
+
/** Builds the url to send the user to, to authorize your application. */
|
|
106
|
+
async createAuthorizationURL(options = {}) {
|
|
107
|
+
const codeVerifier = options.codeVerifier ?? OAuthClient.createCodeVerifier();
|
|
108
|
+
const state = options.state ?? OAuthClient.createState();
|
|
109
|
+
const redirectURI = options.redirectURI ?? this.redirectURI;
|
|
110
|
+
const scopes = options.scopes ?? this.scopes;
|
|
111
|
+
const url = new URL("/oauth/authorize", this.baseURL);
|
|
112
|
+
url.searchParams.set("response_type", "code");
|
|
113
|
+
url.searchParams.set("client_id", this.clientId);
|
|
114
|
+
if (redirectURI)
|
|
115
|
+
url.searchParams.set("redirect_uri", redirectURI);
|
|
116
|
+
if (scopes.length !== 0)
|
|
117
|
+
url.searchParams.set("scope", scopes.join(" "));
|
|
118
|
+
url.searchParams.set("state", state);
|
|
119
|
+
url.searchParams.set("code_challenge", await OAuthClient.createCodeChallenge(codeVerifier));
|
|
120
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
121
|
+
if (options.nonce)
|
|
122
|
+
url.searchParams.set("nonce", options.nonce);
|
|
123
|
+
if (options.prompt)
|
|
124
|
+
url.searchParams.set("prompt", options.prompt);
|
|
125
|
+
return { codeVerifier, redirectURI, state, url: url.toString() };
|
|
126
|
+
}
|
|
127
|
+
/** Gets e621's OpenID Connect discovery document. */
|
|
128
|
+
async discovery() {
|
|
129
|
+
return this._discovery.provider();
|
|
130
|
+
}
|
|
131
|
+
/** Exchanges an authorization code for a token, and stores it. */
|
|
132
|
+
async exchangeCode(code, codeVerifier, redirectURI = this.redirectURI) {
|
|
133
|
+
const token = await this._tokens.create({
|
|
134
|
+
grant_type: "authorization_code",
|
|
135
|
+
code,
|
|
136
|
+
code_verifier: codeVerifier,
|
|
137
|
+
redirect_uri: redirectURI ?? undefined,
|
|
138
|
+
...this._clientAuth(),
|
|
139
|
+
});
|
|
140
|
+
await this.setToken(token);
|
|
141
|
+
return token;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Gets a valid access token, refreshing it first if it's expired. Already bound, so it can be passed
|
|
145
|
+
* directly as {@link Options.authToken}.
|
|
146
|
+
*/
|
|
147
|
+
async getAccessToken() {
|
|
148
|
+
if (this._token === null)
|
|
149
|
+
throw new Error("No OAuth token is available, the user must authorize first.");
|
|
150
|
+
if (this.isExpired)
|
|
151
|
+
return (await this.refresh()).access_token;
|
|
152
|
+
return this._token.access_token;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Validates the url the user was redirected back to (state, and any error e621 sent), then exchanges
|
|
156
|
+
* its code for a token.
|
|
157
|
+
* @param callbackURL - The full callback url, or just its query string.
|
|
158
|
+
*/
|
|
159
|
+
async handleCallback(callbackURL, options) {
|
|
160
|
+
const params = callbackURL instanceof URLSearchParams
|
|
161
|
+
? callbackURL
|
|
162
|
+
: typeof callbackURL === "string" && !/^[a-z][\d+.a-z-]*:/i.test(callbackURL)
|
|
163
|
+
? new URLSearchParams(callbackURL)
|
|
164
|
+
: new URL(callbackURL).searchParams;
|
|
165
|
+
const state = params.get("state");
|
|
166
|
+
const error = params.get("error");
|
|
167
|
+
if (error)
|
|
168
|
+
throw new OAuthCallbackError(error, params.get("error_description"), state);
|
|
169
|
+
if (state !== options.state)
|
|
170
|
+
throw new OAuthCallbackError("invalid_state", "The callback's state does not match the expected state.", state);
|
|
171
|
+
const code = params.get("code");
|
|
172
|
+
if (!code)
|
|
173
|
+
throw new OAuthCallbackError("invalid_request", "The callback is missing the authorization code.", state);
|
|
174
|
+
return this.exchangeCode(code, options.codeVerifier, options.redirectURI ?? this.redirectURI);
|
|
175
|
+
}
|
|
176
|
+
/** Introspects a token - by default the current access token. */
|
|
177
|
+
async introspect(token = this._requireToken().access_token) {
|
|
178
|
+
return this._tokens.introspect({ token, ...this._clientAuth() });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Refreshes the access token, and stores the new token. Concurrent calls share the same request.
|
|
182
|
+
* @param scopes - Narrow the new token to these scopes.
|
|
183
|
+
*/
|
|
184
|
+
async refresh(scopes) {
|
|
185
|
+
if (this._refreshing)
|
|
186
|
+
return this._refreshing;
|
|
187
|
+
const refreshToken = this._requireToken().refresh_token;
|
|
188
|
+
this._refreshing = this._tokens.create({
|
|
189
|
+
grant_type: "refresh_token",
|
|
190
|
+
refresh_token: refreshToken,
|
|
191
|
+
scope: scopes?.join(" "),
|
|
192
|
+
...this._clientAuth(),
|
|
193
|
+
})
|
|
194
|
+
.then(async (token) => {
|
|
195
|
+
await this.setToken(token);
|
|
196
|
+
return token;
|
|
197
|
+
})
|
|
198
|
+
.finally(() => {
|
|
199
|
+
this._refreshing = null;
|
|
200
|
+
});
|
|
201
|
+
return this._refreshing;
|
|
202
|
+
}
|
|
203
|
+
/** Revokes the current token (which also invalidates its refresh token), and clears it. */
|
|
204
|
+
async revoke() {
|
|
205
|
+
const token = this._requireToken();
|
|
206
|
+
await this._tokens.revoke({ token: token.access_token, token_type_hint: "access_token", ...this._clientAuth() });
|
|
207
|
+
await this.setToken(null);
|
|
208
|
+
}
|
|
209
|
+
/** Replaces the stored token, calling {@link OAuthClientOptions.onToken}. */
|
|
210
|
+
async setToken(token) {
|
|
211
|
+
this._token = token;
|
|
212
|
+
await this._onToken?.(token);
|
|
213
|
+
}
|
|
214
|
+
/** Gets metadata about the current access token. */
|
|
215
|
+
async tokenInfo() {
|
|
216
|
+
return this._tokenInfo.get();
|
|
217
|
+
}
|
|
218
|
+
/** Gets the OpenID Connect claims of the authorized user, gated by the token's scopes. */
|
|
219
|
+
async userInfo() {
|
|
220
|
+
return this._userinfo.get();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiT0F1dGhDbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9saWIvT0F1dGhDbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGdCQUFnQixFQUFnQixNQUFNLGFBQWEsQ0FBQztBQUM3RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDakQsT0FBTyxnQ0FBZ0MsTUFBTSwrQ0FBK0MsQ0FBQztBQUM3RixPQUFPLCtCQUErQixNQUFNLDhDQUE4QyxDQUFDO0FBQzNGLE9BQU8sbUJBQW1CLE1BQU0sa0NBQWtDLENBQUM7QUFDbkUsT0FBTyxnQkFBZ0IsTUFBTSwrQkFBK0IsQ0FBQztBQXNGN0QsU0FBUyxTQUFTLENBQUMsS0FBaUI7SUFDaEMsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDO0lBQ2IsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLO1FBQUUsR0FBRyxJQUFJLE1BQU0sQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDM0QsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxVQUFVLENBQUMsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDbEYsQ0FBQztBQUVELFNBQVMsWUFBWSxDQUFDLFVBQVUsR0FBRyxFQUFFO0lBQ2pDLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsSUFBSSxVQUFVLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pFLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sV0FBVztJQUNwQixhQUFhLENBQWlCO0lBQzlCLFVBQVUsQ0FBb0M7SUFDOUMsUUFBUSxDQUFpQztJQUN6QyxXQUFXLENBQThCO0lBQ3pDLE1BQU0sQ0FBcUI7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
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
import type { Client } from "./generated/client/types.js";
|
|
2
2
|
import type { NoV2Options, PostFormatOptions } from "./modules/posts/Format.js";
|
|
3
3
|
/** @category Main */
|
|
4
|
+
export interface RatelimitInfo {
|
|
5
|
+
/** The 1-indexed attempt that just got rate limited. */
|
|
6
|
+
attempt: number;
|
|
7
|
+
/** The configured {@link RatelimitOptions.maxRetries}, for context. */
|
|
8
|
+
maxRetries: number;
|
|
9
|
+
request: Request;
|
|
10
|
+
response: Response;
|
|
11
|
+
/** Seconds parsed from the response's `Retry-After` header - `null` if missing/unparsable. */
|
|
12
|
+
retryAfter: number | null;
|
|
13
|
+
/** Whether this request will be retried, or is about to fail with a {@link RatelimitError}. */
|
|
14
|
+
willRetry: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** @category Main */
|
|
17
|
+
export interface RatelimitOptions {
|
|
18
|
+
/**
|
|
19
|
+
* The maximum number of times to retry a rate limited (HTTP 429) request before giving up and
|
|
20
|
+
* throwing a {@link RatelimitError}.
|
|
21
|
+
* @default 3
|
|
22
|
+
*/
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
/**
|
|
25
|
+
* The maximum delay, in seconds, to wait before retrying, regardless of what the `Retry-After` header
|
|
26
|
+
* says or how many attempts have been made.
|
|
27
|
+
* @default 60
|
|
28
|
+
*/
|
|
29
|
+
maxRetryDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Called every time a request gets rate limited, before the retry delay (or before throwing, if
|
|
32
|
+
* retries are exhausted or disabled). Useful for logging/metrics.
|
|
33
|
+
* @default none
|
|
34
|
+
*/
|
|
35
|
+
onRatelimit?: (info: RatelimitInfo) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to automatically retry requests that get rate limited (HTTP 429), waiting for the duration
|
|
38
|
+
* in the response's `Retry-After` header (or `retryDelay`, backed off exponentially per attempt, if
|
|
39
|
+
* the header is missing or unusable) before retrying.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
retry?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The delay, in seconds, to wait before retrying a rate limited request when the response has no (or
|
|
45
|
+
* an unusable) `Retry-After` header. Doubled per attempt (capped at `maxRetryDelay`).
|
|
46
|
+
* @default 1
|
|
47
|
+
*/
|
|
48
|
+
retryDelay?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A bearer token, or a function returning one (e.g. {@link OAuthClient.getAccessToken}, to refresh tokens
|
|
52
|
+
* as they expire). Returning `null`/`undefined` sends the request without bearer auth.
|
|
53
|
+
* @category Main
|
|
54
|
+
*/
|
|
55
|
+
export type AuthToken = string | (() => string | null | undefined | Promise<string | null | undefined>);
|
|
56
|
+
/** @category Main */
|
|
4
57
|
export interface Options<PF extends PostFormatOptions = NoV2Options> {
|
|
5
58
|
/**
|
|
6
59
|
* The api key you want to authenticate with (Account -> Manage API Access)
|
|
@@ -8,7 +61,13 @@ export interface Options<PF extends PostFormatOptions = NoV2Options> {
|
|
|
8
61
|
*/
|
|
9
62
|
authKey?: string;
|
|
10
63
|
/**
|
|
11
|
-
*
|
|
64
|
+
* A bearer token (such as an OAuth access token) to authenticate with, or a function that returns one.
|
|
65
|
+
* Takes priority over {@link Options.authUser}/{@link Options.authKey} when both are provided.
|
|
66
|
+
* @default none
|
|
67
|
+
*/
|
|
68
|
+
authToken?: AuthToken;
|
|
69
|
+
/**
|
|
70
|
+
* The username of the user you want to authenticate with
|
|
12
71
|
* @default none
|
|
13
72
|
*/
|
|
14
73
|
authUser?: string;
|
|
@@ -25,6 +84,11 @@ export interface Options<PF extends PostFormatOptions = NoV2Options> {
|
|
|
25
84
|
* @default legacy (v2: false)
|
|
26
85
|
*/
|
|
27
86
|
defaultPostFormat?: PF;
|
|
87
|
+
/**
|
|
88
|
+
* Controls how rate limited (HTTP 429) requests are retried.
|
|
89
|
+
* @default \{ retry: true, maxRetries: 3, retryDelay: 1, maxRetryDelay: 60 \}
|
|
90
|
+
*/
|
|
91
|
+
ratelimit?: RatelimitOptions;
|
|
28
92
|
/**
|
|
29
93
|
* The number of seconds before a request times out
|
|
30
94
|
* @default 30
|
|
@@ -49,10 +113,20 @@ export interface Options<PF extends PostFormatOptions = NoV2Options> {
|
|
|
49
113
|
userAgentInQuery?: boolean;
|
|
50
114
|
}
|
|
51
115
|
/** @category Main */
|
|
116
|
+
export interface ResolvedRatelimitOptions {
|
|
117
|
+
maxRetries: number;
|
|
118
|
+
maxRetryDelay: number;
|
|
119
|
+
onRatelimit?: (info: RatelimitInfo) => void;
|
|
120
|
+
retry: boolean;
|
|
121
|
+
retryDelay: number;
|
|
122
|
+
}
|
|
123
|
+
/** @category Main */
|
|
52
124
|
export interface InstanceOptions {
|
|
53
125
|
authKey: string | null;
|
|
126
|
+
authToken: AuthToken | null;
|
|
54
127
|
authUser: string | null;
|
|
55
128
|
baseURL: string;
|
|
129
|
+
ratelimit: ResolvedRatelimitOptions;
|
|
56
130
|
requestTimeout: number;
|
|
57
131
|
statusCheckURL: string;
|
|
58
132
|
userAgent: string;
|
package/dist/lib/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Debug from "./Debug.js";
|
|
2
|
+
import { parseRetryAfter } from "./errors.js";
|
|
2
3
|
import { createClient } from "./generated/client/client.js";
|
|
3
4
|
import { createConfig } from "./generated/client/utils.js";
|
|
4
5
|
import { VERSION } from "./version.js";
|
|
@@ -14,39 +15,59 @@ import { VERSION } from "./version.js";
|
|
|
14
15
|
export function createE621Client(options) {
|
|
15
16
|
const resolved = {
|
|
16
17
|
authKey: options?.authKey ?? null,
|
|
18
|
+
authToken: options?.authToken ?? null,
|
|
17
19
|
authUser: options?.authUser ?? null,
|
|
18
20
|
baseURL: options?.baseURL ?? "https://e621.net",
|
|
21
|
+
ratelimit: {
|
|
22
|
+
maxRetries: options?.ratelimit?.maxRetries ?? 3,
|
|
23
|
+
maxRetryDelay: options?.ratelimit?.maxRetryDelay ?? 60,
|
|
24
|
+
onRatelimit: options?.ratelimit?.onRatelimit,
|
|
25
|
+
retry: options?.ratelimit?.retry ?? true,
|
|
26
|
+
retryDelay: options?.ratelimit?.retryDelay ?? 1,
|
|
27
|
+
},
|
|
19
28
|
requestTimeout: options?.requestTimeout ?? 30,
|
|
20
29
|
statusCheckURL: options?.statusCheckURL ?? "https://status.e621.church/json",
|
|
21
30
|
userAgent: options?.userAgent ?? `E621/${VERSION} (https://github.com/DonovanDMC/E621${options?.authUser ? `; "${options.authUser}"` : ""})`,
|
|
22
31
|
userAgentInQuery: options?.userAgentInQuery ?? "window" in globalThis,
|
|
23
32
|
};
|
|
24
|
-
|
|
25
|
-
const
|
|
33
|
+
// read at request time, so changing options.authToken etc. after construction still takes effect
|
|
34
|
+
const getAuthorization = async () => {
|
|
35
|
+
if (resolved.authToken) {
|
|
36
|
+
const token = typeof resolved.authToken === "function" ? await resolved.authToken() : resolved.authToken;
|
|
37
|
+
if (token)
|
|
38
|
+
return `Bearer ${token}`;
|
|
39
|
+
}
|
|
40
|
+
if (resolved.authKey && resolved.authUser)
|
|
41
|
+
return `Basic ${btoa(`${resolved.authUser}:${resolved.authKey}`)}`;
|
|
42
|
+
return null;
|
|
43
|
+
};
|
|
44
|
+
const config = createConfig({
|
|
26
45
|
baseUrl: resolved.baseURL,
|
|
27
46
|
headers: resolved.userAgentInQuery ? undefined : {
|
|
28
47
|
"User-Agent": resolved.userAgent,
|
|
29
48
|
},
|
|
30
49
|
fetch: async (input, init) => {
|
|
31
50
|
let url = "unknown", method = "unknown";
|
|
32
|
-
// for
|
|
51
|
+
// the generated client only inserts auth for operations with a matching security scheme, so it's done here instead
|
|
33
52
|
if (input instanceof Request) {
|
|
34
53
|
url = input.url;
|
|
35
54
|
method = input.method.toLowerCase();
|
|
36
|
-
if (!input.headers.has("
|
|
37
|
-
|
|
55
|
+
if (!input.headers.has("Authorization")) {
|
|
56
|
+
const authorization = await getAuthorization();
|
|
57
|
+
if (authorization)
|
|
58
|
+
input.headers.set("Authorization", authorization);
|
|
38
59
|
}
|
|
39
60
|
}
|
|
40
61
|
else if (init) {
|
|
41
62
|
url = input.toString();
|
|
42
63
|
method = init.method?.toLowerCase() ?? "get";
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
init.headers = headers;
|
|
64
|
+
const headers = new Headers(init.headers);
|
|
65
|
+
if (!headers.has("Authorization")) {
|
|
66
|
+
const authorization = await getAuthorization();
|
|
67
|
+
if (authorization)
|
|
68
|
+
headers.set("Authorization", authorization);
|
|
49
69
|
}
|
|
70
|
+
init.headers = headers;
|
|
50
71
|
}
|
|
51
72
|
if (resolved.userAgentInQuery && input instanceof Request) {
|
|
52
73
|
const requestURL = new URL(input.url);
|
|
@@ -55,25 +76,36 @@ export function createE621Client(options) {
|
|
|
55
76
|
url = requestURL.toString();
|
|
56
77
|
}
|
|
57
78
|
Debug(`request:${method}`, `-> ${url}`);
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
let attempt = 0, res;
|
|
80
|
+
for (;;) {
|
|
81
|
+
// clone so a retry can re-send the body - `input` itself is never fetched directly, so it's
|
|
82
|
+
// never disturbed and can be cloned again on the next attempt
|
|
83
|
+
res = await fetch(input instanceof Request ? input.clone() : input, init);
|
|
84
|
+
Debug(`response:${method}`, `<- ${url} ${res.status} ${res.statusText}`);
|
|
85
|
+
if (res.status !== 429)
|
|
86
|
+
break;
|
|
87
|
+
const retryAfter = parseRetryAfter(res);
|
|
88
|
+
const willRetry = resolved.ratelimit.retry && attempt < resolved.ratelimit.maxRetries;
|
|
89
|
+
resolved.ratelimit.onRatelimit?.({
|
|
90
|
+
attempt: attempt + 1,
|
|
91
|
+
maxRetries: resolved.ratelimit.maxRetries,
|
|
92
|
+
request: input instanceof Request ? input : new Request(url, init),
|
|
93
|
+
response: res,
|
|
94
|
+
retryAfter,
|
|
95
|
+
willRetry,
|
|
96
|
+
});
|
|
97
|
+
if (!willRetry)
|
|
98
|
+
break;
|
|
99
|
+
const delay = Math.min(retryAfter ?? resolved.ratelimit.retryDelay * 2 ** attempt, resolved.ratelimit.maxRetryDelay);
|
|
100
|
+
Debug(`response:${method}`, `<- ${url} ratelimited, retrying in ${delay}s (attempt ${attempt + 1}/${resolved.ratelimit.maxRetries})`);
|
|
101
|
+
// free the connection, the body of a response we're discarding is never read
|
|
102
|
+
await res.body?.cancel().catch(() => { });
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve, delay * 1000));
|
|
104
|
+
attempt++;
|
|
105
|
+
}
|
|
60
106
|
return res;
|
|
61
107
|
},
|
|
62
|
-
};
|
|
63
|
-
if (resolved.authKey && resolved.authUser) {
|
|
64
|
-
config = createConfig({
|
|
65
|
-
...commonConfig,
|
|
66
|
-
auth: (auth) => {
|
|
67
|
-
if (auth.scheme === "basic") {
|
|
68
|
-
return `${resolved.authUser}:${resolved.authKey}`;
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
config = createConfig(commonConfig);
|
|
76
|
-
}
|
|
108
|
+
});
|
|
77
109
|
return { client: createClient(config), options: resolved };
|
|
78
110
|
}
|
|
79
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
111
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vbGliL2NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssTUFBTSxZQUFZLENBQUM7QUFDL0IsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDNUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQzNELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFxSnZDOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUFtRCxPQUFxQjtJQUNwRyxNQUFNLFFBQVEsR0FBb0I7UUFDOUIsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLElBQUksSUFBSTtRQUNqQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFNBQVMsSUFBSSxJQUFJO1FBQ3JDLFFBQVEsRUFBRSxPQUFPLEVBQUUsUUFBUSxJQUFJLElBQUk7UUFDbkMsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLElBQUksa0JBQWtCO1FBQy9DLFNBQVMsRUFBRTtZQUNQLFVBQVUsRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsSUFBSSxDQUFDO1lBQy9DLGFBQWEsRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLGFBQWEsSUFBSSxFQUFFO1lBQ3RELFdBQVcsRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLFdBQVc7WUFDNUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxJQUFJLElBQUk7WUFDeEMsVUFBVSxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsVUFBVSxJQUFJLENBQUM7U0FDbEQ7UUFDRCxjQUFjLEVBQUUsT0FBTyxFQUFFLGNBQWMsSUFBSSxFQUFFO1FBQzdDLGNBQWMsRUFBRSxPQUFPLEVBQUUsY0FBYyxJQUFJLGlDQUFpQztRQUM1RSxT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
|