From df8a7e3a1c21d216018109c4e73db27988c2a007 Mon Sep 17 00:00:00 2001 From: dsinghvi Date: Wed, 10 Jan 2024 13:46:51 -0500 Subject: [PATCH] add oauth helpers --- .fernignore | 7 +++ README.md | 43 +++++++++------- src/index.ts | 2 +- src/wrapper/WebflowClient.ts | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 src/wrapper/WebflowClient.ts diff --git a/.fernignore b/.fernignore index 084a8ebb..45eb180b 100644 --- a/.fernignore +++ b/.fernignore @@ -1 +1,8 @@ # Specify files that shouldn't be modified by Fern + +README.md + +# Oauth Helpers +src/index.ts +src/oauth.ts +src/wrapper diff --git a/README.md b/README.md index e4fe2d89..cc367c2e 100644 --- a/README.md +++ b/README.md @@ -50,15 +50,12 @@ for more details. ```javascript import { WebflowClient } from "webflow-api"; -const authorizeUrlParams = { +const authorizeUrl = WebflowClient.authorizeURL({ state: "your_state", scope: "sites:read", - client_id: "your_client_id", - redirect_uri: "your_redirect_uri", - response_type: "code", -}; - -const authorizeUrl = webflowClient.authorizeUrl(authorizeUrlParams); // not yet implemented + clientId: "your_client_id", + redirctUri: "your_redirect_uri", +}); console.log(authorizeUrl); ``` @@ -71,11 +68,11 @@ Use the `getAccessToken` function and pass in your `client_id`, ```javascript import { WebflowClient } from "webflow-api"; -const accessToken = WebflowClient.getAccessToken( - (client_id = "YOUR_CLIENT_ID"), - (client_secret = "YOUR_CLIENT_SECRET"), - (code = "YOUR_AUTHORIZATION_CODE") -); // not yet implemented +const accessToken = WebflowClient.getAccessToken({ + clientId: "your_client_id", + clientSecret: "your_client_secret", + code: "your_authorization_code" +}); ``` ### Step 3: Instantiate the client @@ -90,7 +87,7 @@ const webflow = WebflowClient({ accessToken }); ## Webflow Types -All of the types are nested within the `Webflow` object. Let IntelliSense +All of the types are nested within the `Webflow` namespace. Let IntelliSense guide you! ## Exception Handling @@ -123,12 +120,20 @@ By default, requests time out after 60 seconds. You can configure the timeout an ```javascript import { WebflowClient } from 'webflow'; -const webflow = new WebflowClient({ - accessToken: 'your_access_token', - requestOptions: { - timeoutInSeconds: 10, - maxRetries: 3, - } +const sites = await webflow.sites.get(..., { + timeoutInSeconds: 30 // override the timeout +}); +``` + +### Retries +The SDK will automatically retry failures with exponential backoff. +You can configure the retries by setting maxRetries. + +```javascript +import { WebflowClient } from 'webflow'; + +const sites = await webflow.sites.get(..., { + maxRetries: 10 // override the reries }); ``` diff --git a/src/index.ts b/src/index.ts index 2fb7df71..a1b94f2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export * as Webflow from "./api"; -export { WebflowClient } from "./Client"; +export { WebflowClient } from "./wrapper/WebflowClient"; export { WebflowEnvironment } from "./environments"; export { WebflowError, WebflowTimeoutError } from "./errors"; diff --git a/src/wrapper/WebflowClient.ts b/src/wrapper/WebflowClient.ts new file mode 100644 index 00000000..ee7fcfc1 --- /dev/null +++ b/src/wrapper/WebflowClient.ts @@ -0,0 +1,96 @@ +import { WebflowClient as FernClient } from "../Client"; +import { OauthScope } from "../api/types"; +import * as errors from "../errors"; +import * as core from "../core"; + +export class WebflowClient extends FernClient { + /** + * @param clientId The OAuth client ID + * @param state The state + * @param redirectUri The redirect URI + * @param scope The OAuth scopes + * @returns the URL to authorize a user + */ + static authorizeURL({ + clientId, + state, + redirectUri, + scope, + }: { + clientId: string; + state?: string; + redirectUri?: string; + scope: OauthScope | OauthScope[]; + }): string { + const params: Record = { response_type: "code", client_id: clientId }; + if (redirectUri != null) { + params["redirect_uri"] = redirectUri; + } + if (state != null) { + params["state"] = state; + } + if (typeof scope === "string") { + params["scope"] = scope; + } else { + params["scope"] = scope.join("+"); + } + return `https://webflow.com/oauth/authorize?${qs.stringify(params)}`; + } + + /** + * @param clientId The OAuth client ID + * @param clientSecret The OAuth client secret + * @param code The OAuth code + * @param redirect_uri The redirect uri + * @returns access token that can be used to hit our API + */ + static async getAccessToken({ + clientId, + clientSecret, + code, + redirectUri, + }: { + clientId: string; + clientSecret: string; + code: string; + redirectUri?: string; + }): Promise { + const body: Record = { + client_id: clientId, + client_secret: clientSecret, + code, + grant_type: "authorization_code", + }; + if (redirectUri != null) { + body["redirect_uri"] = redirectUri; + } + const response = await core.fetcher({ + url: "https://api.webflow.com/oauth/access_token", + method: "POST", + contentType: "application/json", + body, + }); + if (response.ok) { + return (response as any)["access_token"]; + } + + switch (response.error.reason) { + case "status-code": + throw new errors.WebflowError({ + statusCode: response.error.statusCode, + body: response.error.body, + }); + case "non-json": + throw new errors.WebflowError({ + statusCode: response.error.statusCode, + body: response.error.rawBody, + }); + case "timeout": + throw new errors.WebflowTimeoutError(); + case "unknown": + throw new errors.WebflowError({ + message: response.error.errorMessage, + }); + } + } +}