diff --git a/docs/utils-reference/oauth/OAuthService.md b/docs/utils-reference/oauth/OAuthService.md index d2186b0..134ab59 100644 --- a/docs/utils-reference/oauth/OAuthService.md +++ b/docs/utils-reference/oauth/OAuthService.md @@ -38,8 +38,8 @@ Initiates the OAuth authorization process or refreshes existing tokens if necess ##### Signature -```typescript -authorize(): Promise; +```ts +OAuthService.authorize(): Promise; ``` ##### Example @@ -50,7 +50,7 @@ const accessToken = await oauthService.authorize(); ### Built-in Services -We expose by default some services using `OAuthService` to make it easy to authenticate with them: +Some services are exposed as static properties in `OAuthService` to make it easy to authenticate with them: - [Asana](#asana) - [GitHub](#github) @@ -60,131 +60,136 @@ We expose by default some services using `OAuthService` to make it easy to authe - [Slack](#slack) - [Zoom](#zoom) -Some of these services already have a default client configured so that you only have to specify the permission scopes. +Asana, GitHub, Linear, and Slack already have an OAuth app configured by Raycast so that you can use them right of the box by specifing only the permission scopes. You are still free to create an OAuth app for them if you want. + +Google, Jira and Zoom don't have an OAuth app configured by Raycast so you'll have to create one if you want to use them. + +Use [ProviderOptions](#provideroptions) or [ProviderWithDefaultClientOptions](#providerwithdefaultclientoptions) to configure these built-in services. #### Asana +##### Signature + +```ts +OAuthService.asana: (options: ProviderWithDefaultClientOptions) => OAuthService +``` + +##### Example + ```tsx -const asana = OAuthService.asana({ - clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Asana - scope: 'default', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly -}); +const asana = OAuthService.asana({ scope: 'default' }) ``` #### GitHub +##### Signature + +```ts +OAuthService.github: (options: ProviderWithDefaultClientOptions) => OAuthService +``` + +##### Example + ```tsx -const github = OAuthService.github({ - clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for GitHub - scope: 'repo user', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly -}); +const github = OAuthService.github({ scope: 'repo user' }) ``` #### Google -{% hint style="info" %} Google has verification processes based on the required scopes for your extension. Therefore, you need to configure your own client for it. -{% endhint %} + +##### Signature + +```ts +OAuthService.google: (options: ProviderOptions) => OAuthService +``` + +##### Example ```tsx const google = OAuthService.google({ clientId: 'custom-client-id', authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth', tokenUrl: 'https://oauth2.googleapis.com/token', - scope: 'https://www.googleapis.com/auth/drive.readonly', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly + scope: 'https://www.googleapis.com/auth/drive.readonly', }); ``` #### Jira -{% hint style="info" %} Jira requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it. -{% endhint %} + +##### Signature + +```ts +OAuthService.jira: (options: ProviderOptions) => OAuthService +``` + +##### Example ```tsx const jira = OAuthService.jira({ clientId: 'custom-client-id', authorizeUrl: 'https://auth.atlassian.com/authorize', tokenUrl: 'https://api.atlassian.com/oauth/token', - scope: 'read:jira-user read:jira-work offline_access', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly + scope: 'read:jira-user read:jira-work offline_access' }); ``` #### Linear +##### Signature + +```ts +OAuthService.linear: (options: ProviderOptions) => OAuthService +``` + +##### Example + ```tsx -const linear = OAuthService.linear({ - clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Linear - scope: 'read write', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly -}); +const linear = OAuthService.linear({ scope: 'read write' }) ``` #### Slack +##### Signature + +```ts +OAuthService.slack: (options: ProviderWithDefaultClientOptions) => OAuthService +``` + +##### Example + ```tsx -const slack = OAuthService.slack({ - clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Slack - scope: 'emoji:read', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly -}); +const slack = OAuthService.slack({ scope: 'emoji:read' }) ``` #### Zoom -{% hint style="info" %} Zoom requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it. -{% endhint %} + +##### Signature + +```ts +OAuthService.zoom: (options: ProviderOptions) => OAuthService +``` + +##### Example ```tsx const zoom = OAuthService.zoom({ clientId: 'custom-client-id', authorizeUrl: 'https://zoom.us/oauth/authorize', tokenUrl: 'https://zoom.us/oauth/token', - scope: 'meeting:write', // Specify the scopes your application requires - personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly + scope: 'meeting:write', + personalAccessToken: 'personal-access-token', }); ``` -## Subclassing - -You can subclass `OAuthService` to create a tailored service for other OAuth providers by setting predefined defaults. - -Here's an example: - -```ts -export class CustomOAuthService extends OAuthService { - constructor(options: ClientConstructor) { - super({ - client: new OAuth.PKCEClient({ - redirectMethod: OAuth.RedirectMethod.Web, - providerName: "PROVIDER_NAME", - providerIcon: "provider.png", - providerId: "PROVIDER-ID", - description: "Connect your {PROVIDER_NAME} account", - }), - clientId: "YOUR_CLIENT_ID", - authorizeUrl: "YOUR_AUTHORIZE_URL", - tokenUrl: "YOUR_TOKEN_URL", - scope: "YOUR_SCOPES" - extraParameters: { - actor: "user", - }, - }); - } -} -``` - ## Types ### OAuthServiceOptions -Here's an updated markdown table with a "Type" column: - | Property Name | Description | Type | |---------------|-------------|------| | client* | The PKCE Client defined using `OAuth.PKCEClient` from `@raycast/api` | `OAuth.PKCEClient` | @@ -194,5 +199,30 @@ Here's an updated markdown table with a "Type" column: | tokenUrl* | The URL to exchange the authorization code for an access token | `string` | | refreshTokenUrl | The URL to refresh the access token if applicable | `string` | | personalAccessToken | A personal token if the provider supports it | `string` | +| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | | extraParameters | The extra parameters you may need for the authorization request | `Record` | | bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` | + +### ProviderOptions + +| Property Name | Description | Type | +|---------------|-------------|------| +| clientId* | The app's client ID | `string` | +| scope* | The scope of the access requested from the provider | `string` | +| authorizeUrl* | The URL to start the OAuth flow | `string` | +| tokenUrl* | The URL to exchange the authorization code for an access token | `string` | +| refreshTokenUrl | The URL to refresh the access token if applicable | `string` | +| personalAccessToken | A personal token if the provider supports it | `string` | +| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | + +### ProviderWithDefaultClientOptions + +| Property Name | Description | Type | +|---------------|-------------|------| +| scope* | The scope of the access requested from the provider | `string` | +| clientId | The app's client ID | `string` | +| authorizeUrl | The URL to start the OAuth flow | `string` | +| tokenUrl | The URL to exchange the authorization code for an access token | `string` | +| refreshTokenUrl | The URL to refresh the access token if applicable | `string` | +| personalAccessToken | A personal token if the provider supports it | `string` | +| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` | \ No newline at end of file diff --git a/docs/utils-reference/oauth/README.md b/docs/utils-reference/oauth/README.md index 9ef04ff..2d6622f 100644 --- a/docs/utils-reference/oauth/README.md +++ b/docs/utils-reference/oauth/README.md @@ -2,14 +2,14 @@ Dealing with OAuth can be tedious. So we've built a set of utilities to make that task way easier. There's two part to our utilities: -1. Authenticating with the service using [OAuthService](./OAuthService.md) or some built-in providers (e.g GitHub with `OAuthService.github`) +1. Authenticating with a service using [OAuthService](./OAuthService.md) or built-in providers (e.g GitHub with `OAuthService.github`) 2. Bringing authentication to Raycast commands using [withAccessToken](./withAccessToken.md) and [`getAccessToken`](./getAccessToken.md) -Here are two different use-cases where you can use the utilities. +`OAuthService`, `withAccessToken`, and `getAccessToken` are designed to work together. You'll find below two different use-cases for which you can use these utils. ## Using a built-in provider -We provide 3rd party providers that you can use out of the box such as GitHub or Linear. Here's how you can use them: +We provide built-in providers that you can use out of the box, such as GitHub or Linear. You don't need to configure anything for them apart from the scope your extension requires. ```tsx import { Detail, LaunchProps } from "@raycast/api"; @@ -27,6 +27,8 @@ function AuthorizedComponent(props: LaunchProps) { export default withAccessToken(github)(AuthorizedComponent); ``` +You can see our different providers in the following page: [OAuthService](./OAuthService.md) + ## Using your own client ```tsx @@ -44,7 +46,7 @@ const client = new OAuth.PKCEClient({ const provider = new OAuthService({ client, clientId: "YOUR_CLIENT_ID", - scopes: "YOUR SCOPES", + scopes: "YOUR_SCOPES", authorizeUrl: "YOUR_AUTHORIZE_URL", tokenUrl: "YOUR_TOKEN_URL", }); diff --git a/docs/utils-reference/oauth/getAccessToken.md b/docs/utils-reference/oauth/getAccessToken.md index b2c4c5a..6064946 100644 --- a/docs/utils-reference/oauth/getAccessToken.md +++ b/docs/utils-reference/oauth/getAccessToken.md @@ -23,8 +23,6 @@ The function returns an object containing the following properties: ## Example -Here's a simple example: - ```tsx import { Detail } from "@raycast/api"; import { authorize } from "./oauth" diff --git a/docs/utils-reference/oauth/withAccessToken.md b/docs/utils-reference/oauth/withAccessToken.md index 2e5d888..edf4f9e 100644 --- a/docs/utils-reference/oauth/withAccessToken.md +++ b/docs/utils-reference/oauth/withAccessToken.md @@ -15,10 +15,10 @@ function withAccessToken( ### Arguments `options` is an object containing: -- `options.authorize` is a function that initiates the OAuth token retrieval process. It returns a promise that resolves to an access token. -- `options.personalAccessToken` is an optional string that represents an already obtained personal access token. When `options.personalAccessToken` is provided, it uses that token. Otherwise, it calls `options.authorize` to fetch an OAuth token asynchronously. -- `options.client` is an optional instance of a PKCE Client that you can create using Raycast API. This client is used to return the `idToken` as part of the `onAuthorize` callback below. -- `options.onAuthorize` is an optional callback function that is called once the user has been properly logged in through OAuth. This function is called with the `token`, its type (whether it comes from an OAuth flow or if it's a personal access token) and an `idToken` if `options.client` is provided and if it's returned in the initial token set. +- `options.authorize`: a function that initiates the OAuth token retrieval process. It returns a promise that resolves to an access token. +- `options.personalAccessToken`: an optional string that represents an already obtained personal access token. When `options.personalAccessToken` is provided, it uses that token. Otherwise, it calls `options.authorize` to fetch an OAuth token asynchronously. +- `options.client`: an optional instance of a PKCE Client that you can create using Raycast API. This client is used to return the `idToken` as part of the `onAuthorize` callback below. +- `options.onAuthorize`: an optional callback function that is called once the user has been properly logged in through OAuth. This function is called with the `token`, its type (`oauth` if it comes from an OAuth flow or `personal` if it's a personal access token), and `idToken` if it's returned from `options.client`'s initial token set. ### Return @@ -30,7 +30,6 @@ Note that the access token isn't injected into the wrapped component props. Inst ## Example - {% tabs %} {% tab title="view.tsx" %} @@ -108,4 +107,10 @@ type WithAccessTokenParameters = { personalAccessToken?: string; onAuthorize?: (params: OnAuthorizeParams) => void; }; +``` + +### WithAccessTokenComponentOrFn + +```ts +type WithAccessTokenComponentOrFn = ((params: T) => Promise | void) | React.ComponentType; ``` \ No newline at end of file diff --git a/src/oauth/OAuthService.ts b/src/oauth/OAuthService.ts index c671a67..0a23b09 100644 --- a/src/oauth/OAuthService.ts +++ b/src/oauth/OAuthService.ts @@ -82,11 +82,7 @@ export class OAuthService implements OAuthServiceOptions { * * @example * ```typescript - * const asana = OAuthService.asana({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'default', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly - * }); + * const asana = OAuthService.asana({ scope: 'default' }) * ``` */ public static asana = asanaService; @@ -96,11 +92,7 @@ export class OAuthService implements OAuthServiceOptions { * * @example * ```typescript - * const github = OAuthService.github({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'repo user', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly - * }); + * const github = OAuthService.github({ scope: 'repo user' }) * ``` */ public static github = githubService; @@ -111,9 +103,10 @@ export class OAuthService implements OAuthServiceOptions { * @example * ```typescript * const google = OAuthService.google({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'https://www.googleapis.com/auth/drive.readonly', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly + * clientId: 'custom-client-id', + * authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth', + * tokenUrl: 'https://oauth2.googleapis.com/token', + * scope: 'https://www.googleapis.com/auth/drive.readonly', * }); * ``` */ @@ -125,9 +118,10 @@ export class OAuthService implements OAuthServiceOptions { * @example * ```typescript * const jira = OAuthService.jira({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'read:jira-user read:jira-work', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API using a personal token + * clientId: 'custom-client-id', + * authorizeUrl: 'https://auth.atlassian.com/authorize', + * tokenUrl: 'https://api.atlassian.com/oauth/token', + * scope: 'read:jira-user read:jira-work offline_access' * }); * ``` */ @@ -138,11 +132,7 @@ export class OAuthService implements OAuthServiceOptions { * * @example * ```typescript - * const linear = OAuthService.linear({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'read write', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API using a personal token - * }); + * const linear = OAuthService.linear({ scope: 'read write' }) * ``` */ public static linear = linearService; @@ -152,11 +142,7 @@ export class OAuthService implements OAuthServiceOptions { * * @example * ```typescript - * const slack = OAuthService.slack({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: 'emoji:read', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API using a personal token - * }); + * const slack = OAuthService.slack({ scope: 'emoji:read' }) * ``` */ public static slack = slackService; @@ -167,9 +153,11 @@ export class OAuthService implements OAuthServiceOptions { * @example * ```typescript * const zoom = OAuthService.zoom({ - * clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID - * scope: '', // Specify the scopes your application requires - * personalAccessToken: 'personal-access-token', // Optional: For accessing the API using a personal token + * clientId: 'custom-client-id', + * authorizeUrl: 'https://zoom.us/oauth/authorize', + * tokenUrl: 'https://zoom.us/oauth/token', + * scope: 'meeting:write', + * personalAccessToken: 'personal-access-token', * }); * ``` */ diff --git a/src/oauth/withAccessToken.tsx b/src/oauth/withAccessToken.tsx index 114b473..ad128f9 100644 --- a/src/oauth/withAccessToken.tsx +++ b/src/oauth/withAccessToken.tsx @@ -40,6 +40,9 @@ type WithAccessTokenParameters = { onAuthorize?: (params: OnAuthorizeParams) => void; }; +/** + * The component (for a view/menu-bar commands) or function (for a no-view command) that is passed to withAccessToken. + */ export type WithAccessTokenComponentOrFn = ((params: T) => Promise | void) | React.ComponentType; /**