Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitter-api-sdk",
"version": "1.0.4",
"version": "1.0.5",
"description": "A TypeScript SDK for the Twitter API",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
73 changes: 44 additions & 29 deletions src/OAuth2User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,37 @@ interface RevokeAccessTokenResponse {
revoked: boolean;
}

interface GetTokenResponse {
/** Allows an application to obtain a new access token without prompting the user via the refresh token flow. */
refresh_token?: string;
/** Access tokens are the token that applications use to make API requests on behalf of a user. */
access_token?: string;
token_type?: string;
expires_in?: number;
/** Comma-separated list of scopes for the token */
scope?: string;
}

interface Token extends Omit<GetTokenResponse, 'expires_in'> {
/** Date that the access_token will expire at. */
expires_at?: Date;
}

function processTokenResponse(token: GetTokenResponse): Token {
const { expires_in, ...rest } = token;
return {
...rest,
...(!!expires_in && {
expires_at: new Date(Date.now() + expires_in * 1000),
}),
};
}

/**
* Twitter OAuth2 Authentication Client
*/
export class OAuth2User implements AuthClient {
#access_token?: string;
token_type?: string;
expires_at?: Date;
scope?: string;
refresh_token?: string;
token?: Token;
#options: OAuth2UserOptions;
#code_verifier?: string;
#code_challenge?: string;
Expand All @@ -96,16 +118,16 @@ export class OAuth2User implements AuthClient {
/**
* Refresh the access token
*/
async refreshAccessToken(): Promise<void> {
const refresh_token = this.refresh_token;
async refreshAccessToken(): Promise<{ token: Token }> {
const refresh_token = this.token?.refresh_token;
const { client_id, client_secret, request_options } = this.#options;
if (!client_id) {
throw new Error("client_id is required");
}
if (!refresh_token) {
throw new Error("refresh_token is required");
}
const data = await rest({
const data = await rest<GetTokenResponse>({
...request_options,
endpoint: `/2/oauth2/token`,
params: {
Expand All @@ -122,26 +144,17 @@ export class OAuth2User implements AuthClient {
}),
},
});
this.updateToken(data);
}

/**
* Update token information
*/
updateToken(data: Record<string, any>): void {
this.refresh_token = data.refresh_token;
this.#access_token = data.access_token;
this.token_type = data.token_type;
this.expires_at = new Date(Date.now() + data.expires_in * 1000);
this.scope = data.scope;
const token = processTokenResponse(data);
this.token = token;
return { token };
}

/**
* Check if an access token is expired
*/
isAccessTokenExpired(): boolean {
const refresh_token = this.refresh_token;
const expires_at = this.expires_at;
const refresh_token = this.token?.refresh_token;
const expires_at = this.token?.expires_at;
return (
!!refresh_token &&
(!expires_at || expires_at <= new Date(Date.now() + 1000))
Expand All @@ -151,7 +164,7 @@ export class OAuth2User implements AuthClient {
/**
* Request an access token
*/
async requestAccessToken(code?: string): Promise<void> {
async requestAccessToken(code?: string): Promise<{ token: Token }> {
const { client_id, client_secret, callback, request_options } =
this.#options;
const code_verifier = this.#code_verifier;
Expand All @@ -168,7 +181,7 @@ export class OAuth2User implements AuthClient {
client_id,
redirect_uri: callback,
};
const data = await rest({
const data = await rest<GetTokenResponse>({
...request_options,
endpoint: `/2/oauth2/token`,
params,
Expand All @@ -181,16 +194,18 @@ export class OAuth2User implements AuthClient {
}),
},
});
this.updateToken(data);
const token = processTokenResponse(data);
this.token = token;
return { token };
}

/**
* Revoke an access token
*/
async revokeAccessToken(): Promise<RevokeAccessTokenResponse> {
const { client_id, client_secret, request_options } = this.#options;
const access_token = this.#access_token;
const refresh_token = this.refresh_token;
const access_token = this.token?.access_token;
const refresh_token = this.token?.refresh_token;
if (!client_id) {
throw new Error("client_id is required");
}
Expand Down Expand Up @@ -252,10 +267,10 @@ export class OAuth2User implements AuthClient {
}

async getAuthHeader(): Promise<AuthHeader> {
if (!this.#access_token) throw new Error("You do not have an access token");
if (!this.token?.access_token) throw new Error("access_token is required");
if (this.isAccessTokenExpired()) await this.refreshAccessToken();
return {
Authorization: `Bearer ${this.#access_token}`,
Authorization: `Bearer ${this.token.access_token}`,
};
}
}
2 changes: 1 addition & 1 deletion src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function request({
url.toString(),
{
headers: {
"User-Agent": `twitter-api-typescript-sdk/1.0.4`,
"User-Agent": `twitter-api-typescript-sdk/1.0.5`,
...(isPost
? { "Content-Type": "application/json; charset=utf-8" }
: undefined),
Expand Down