diff --git a/docs/utils-reference/oauth/OAuthService.md b/docs/utils-reference/oauth/OAuthService.md
index ead8f78..6eb55dd 100644
--- a/docs/utils-reference/oauth/OAuthService.md
+++ b/docs/utils-reference/oauth/OAuthService.md
@@ -102,7 +102,6 @@ Google has verification processes based on the required scopes for your extensio
Creating your own Google client ID is more tedious than other processes, so we’ve created a page to assist you: [Getting a Google client ID](./getting-google-client-id.md)
{% endhint %}
-
##### Signature
```ts
@@ -142,7 +141,7 @@ const jira = OAuthService.jira({
##### Signature
```ts
-OAuthService.linear: (options: ProviderOptions) => OAuthService
+OAuthService.linear: (options: ProviderWithDefaultClientOptions) => OAuthService
```
##### Example
@@ -154,10 +153,14 @@ const linear = OAuthService.linear({ scope: "read write" });
const secondWorkspace = OAuthService.linear({
scope: "read write",
providerId: "linear-workspace-2",
+ providerName: "Linear — Acme",
+ description: "Connect to Acme as you@acme.com",
extraParameters: { prompt: "consent" },
});
```
+`providerId` is the stable token-storage namespace. `providerName` and `description` are display metadata for the OAuth client, so they can identify the specific account, workspace, or site without changing where its tokens are stored.
+
#### Slack
##### Signature
@@ -204,7 +207,7 @@ const zoom = OAuthService.zoom({
| 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` |
+| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `(params: OnAuthorizeParams) => void` |
| 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` |
| tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
@@ -212,34 +215,38 @@ const zoom = OAuthService.zoom({
### ProviderOptions
-| Property Name | Description | Type |
-| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
-| clientId\* | The app's client ID | `string` |
-| scope\* | The scope of the access requested from the provider | `string` \| `Array` |
-| providerId | The provider ID used to namespace the token storage of the internally-constructed `OAuth.PKCEClient`. Defaults to the service's name (e.g. `linear`). Override it to keep several independent logins for the same provider (e.g. one per workspace). | `string` |
-| extraParameters | Extra parameters for the authorization request, merged over the provider's defaults (caller wins) | `Record` |
-| 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` |
-| bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` |
-| tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
-| tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
+| Property Name | Description | Type |
+| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
+| clientId\* | The app's client ID | `string` |
+| scope\* | The scope of the access requested from the provider | `string` |
+| providerId | The provider ID used to namespace the token storage of the internally-constructed `OAuth.PKCEClient`. Defaults to the service's name (e.g. `linear`). Override it to keep several independent logins for the same provider (e.g. one per workspace). | `string` |
+| providerName | The provider name displayed by the internally-constructed `OAuth.PKCEClient`. Defaults to the built-in provider's name. Override it to distinguish multiple accounts, workspaces, or sites. | `string` |
+| description | The description displayed by the internally-constructed `OAuth.PKCEClient`. Defaults to the built-in provider's connection message. | `string` |
+| extraParameters | Extra parameters for the authorization request, merged over the provider's defaults (caller wins) | `Record` |
+| 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` | `(params: OnAuthorizeParams) => void` |
+| bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` |
+| tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
+| tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
### ProviderWithDefaultClientOptions
-| Property Name | Description | Type |
-| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
-| scope\* | The scope of the access requested from the provider | `string` \| `Array` |
+| Property Name | Description | Type |
+| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
+| scope\* | The scope of the access requested from the provider | `string` |
| providerId | The provider ID used to namespace the token storage of the internally-constructed `OAuth.PKCEClient`. Defaults to the service's name (e.g. `linear`). Override it to keep several independent logins for the same provider (e.g. one per workspace). | `string` |
-| extraParameters | Extra parameters for the authorization request, merged over the provider's defaults (caller wins) | `Record` |
-| 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` |
-| bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` |
-| tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
-| tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
+| providerName | The provider name displayed by the internally-constructed `OAuth.PKCEClient`. Defaults to the built-in provider's name. Override it to distinguish multiple accounts, workspaces, or sites. | `string` |
+| description | The description displayed by the internally-constructed `OAuth.PKCEClient`. Defaults to the built-in provider's connection message. | `string` |
+| extraParameters | Extra parameters for the authorization request, merged over the provider's defaults (caller wins) | `Record` |
+| 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` | `(params: OnAuthorizeParams) => void` |
+| bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` |
+| tokenResponseParser | Some providers returns some non-standard token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
+| tokenRefreshResponseParser | Some providers returns some non-standard refresh token responses. Specifies how to parse the JSON response to get the access token | `(response: unknown) => OAuth.TokenResponse` |
diff --git a/src/oauth/OAuthService.ts b/src/oauth/OAuthService.ts
index 5070804..4dd71d7 100644
--- a/src/oauth/OAuthService.ts
+++ b/src/oauth/OAuthService.ts
@@ -74,12 +74,12 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "Asana",
+ providerName: options.providerName ?? "Asana",
providerIcon: `data:image/svg+xml,${encodeURIComponent(
``,
)}`,
providerId: options.providerId ?? "asana",
- description: "Connect your Asana account",
+ description: options.description ?? "Connect your Asana account",
}),
clientId: options.clientId ?? PROVIDER_CLIENT_IDS.asana,
authorizeUrl: options.authorizeUrl ?? "https://asana.oauth.raycast.com/authorize",
@@ -107,7 +107,7 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "GitHub",
+ providerName: options.providerName ?? "GitHub",
providerIcon: {
source: `data:image/svg+xml,${encodeURIComponent(
``,
@@ -116,7 +116,7 @@ export class OAuthService implements OAuthServiceOptions {
tintColor: Color.PrimaryText,
},
providerId: options.providerId ?? "github",
- description: "Connect your GitHub account",
+ description: options.description ?? "Connect your GitHub account",
}),
clientId: options.clientId ?? PROVIDER_CLIENT_IDS.github,
authorizeUrl: options.authorizeUrl ?? "https://github.oauth.raycast.com/authorize",
@@ -149,12 +149,12 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.AppURI,
- providerName: "Google",
+ providerName: options.providerName ?? "Google",
providerIcon: `data:image/svg+xml,${encodeURIComponent(
``,
)}`,
providerId: options.providerId ?? "google",
- description: "Connect your Google account",
+ description: options.description ?? "Connect your Google account",
}),
clientId: options.clientId,
authorizeUrl: options.authorizeUrl ?? "https://accounts.google.com/o/oauth2/v2/auth",
@@ -187,12 +187,12 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "Jira",
+ providerName: options.providerName ?? "Jira",
providerIcon: `data:image/svg+xml,${encodeURIComponent(
``,
)}`,
providerId: options.providerId ?? "jira",
- description: "Connect your Jira account",
+ description: options.description ?? "Connect your Jira account",
}),
clientId: options.clientId,
authorizeUrl: options.authorizeUrl ?? "https://auth.atlassian.com/authorize",
@@ -220,7 +220,7 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "Linear",
+ providerName: options.providerName ?? "Linear",
providerIcon: {
source: {
light: `data:image/svg+xml,${encodeURIComponent(
@@ -232,7 +232,7 @@ export class OAuthService implements OAuthServiceOptions {
},
},
providerId: options.providerId ?? "linear",
- description: "Connect your Linear account",
+ description: options.description ?? "Connect your Linear account",
}),
clientId: options.clientId ?? PROVIDER_CLIENT_IDS.linear,
authorizeUrl: options.authorizeUrl ?? "https://linear.oauth.raycast.com/authorize",
@@ -262,12 +262,12 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "Slack",
+ providerName: options.providerName ?? "Slack",
providerIcon: `data:image/svg+xml,${encodeURIComponent(
``,
)}`,
providerId: options.providerId ?? "slack",
- description: "Connect your Slack account",
+ description: options.description ?? "Connect your Slack account",
}),
clientId: options.clientId ?? PROVIDER_CLIENT_IDS.slack,
authorizeUrl: options.authorizeUrl ?? "https://slack.oauth.raycast.com/authorize",
@@ -311,12 +311,12 @@ export class OAuthService implements OAuthServiceOptions {
return new OAuthService({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
- providerName: "Zoom",
+ providerName: options.providerName ?? "Zoom",
providerIcon: `data:image/svg+xml,${encodeURIComponent(
``,
)}`,
providerId: options.providerId ?? "zoom",
- description: "Connect your Zoom account",
+ description: options.description ?? "Connect your Zoom account",
}),
clientId: options.clientId,
authorizeUrl: options.authorizeUrl ?? "https://zoom.us/oauth/authorize",
diff --git a/src/oauth/types.ts b/src/oauth/types.ts
index aa27dce..c8735b4 100644
--- a/src/oauth/types.ts
+++ b/src/oauth/types.ts
@@ -26,6 +26,8 @@ export interface OAuthServiceOptions {
type BaseProviderOptions = {
scope: string;
providerId?: string;
+ providerName?: string;
+ description?: string;
extraParameters?: Record;
personalAccessToken?: string;
authorizeUrl?: string;
diff --git a/test/OAuthService.test.ts b/test/OAuthService.test.ts
index 89c921f..9a0d4ca 100644
--- a/test/OAuthService.test.ts
+++ b/test/OAuthService.test.ts
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { OAuthService } from "../src/oauth/OAuthService";
-type MockedClient = { options: { providerId?: string; providerName?: string } };
+type MockedClient = { options: { providerId?: string; providerName: string; description?: string } };
function clientOptions(service: OAuthService) {
return (service.client as unknown as MockedClient).options;
@@ -46,24 +46,98 @@ describe("OAuthService built-in providers", () => {
expect(service.extraParameters).toEqual({ audience: "api.atlassian.com" });
});
- // Table test: the change touches all seven providers — each must keep its default
- // providerId when the option is omitted and accept an override when given.
- const providers: Array<{ name: string; make: (extra?: object) => OAuthService }> = [
- { name: "asana", make: (extra = {}) => OAuthService.asana({ scope: "default", ...extra }) },
- { name: "github", make: (extra = {}) => OAuthService.github({ scope: "repo", ...extra }) },
- { name: "google", make: (extra = {}) => OAuthService.google({ clientId: "cid", scope: "email", ...extra }) },
- { name: "jira", make: (extra = {}) => OAuthService.jira({ clientId: "cid", scope: "read:jira-user", ...extra }) },
- { name: "linear", make: (extra = {}) => OAuthService.linear({ scope: "read write", ...extra }) },
- { name: "slack", make: (extra = {}) => OAuthService.slack({ scope: "emoji:read", ...extra }) },
- { name: "zoom", make: (extra = {}) => OAuthService.zoom({ clientId: "cid", scope: "meeting:write", ...extra }) },
+ // Table tests cover all seven built-in providers so an omitted option always preserves
+ // the existing PKCE client metadata and each option can be overridden independently.
+ const providers: Array<{
+ id: string;
+ providerName: string;
+ description: string;
+ make: (extra?: object) => OAuthService;
+ }> = [
+ {
+ id: "asana",
+ providerName: "Asana",
+ description: "Connect your Asana account",
+ make: (extra = {}) => OAuthService.asana({ scope: "default", ...extra }),
+ },
+ {
+ id: "github",
+ providerName: "GitHub",
+ description: "Connect your GitHub account",
+ make: (extra = {}) => OAuthService.github({ scope: "repo", ...extra }),
+ },
+ {
+ id: "google",
+ providerName: "Google",
+ description: "Connect your Google account",
+ make: (extra = {}) => OAuthService.google({ clientId: "cid", scope: "email", ...extra }),
+ },
+ {
+ id: "jira",
+ providerName: "Jira",
+ description: "Connect your Jira account",
+ make: (extra = {}) => OAuthService.jira({ clientId: "cid", scope: "read:jira-user", ...extra }),
+ },
+ {
+ id: "linear",
+ providerName: "Linear",
+ description: "Connect your Linear account",
+ make: (extra = {}) => OAuthService.linear({ scope: "read write", ...extra }),
+ },
+ {
+ id: "slack",
+ providerName: "Slack",
+ description: "Connect your Slack account",
+ make: (extra = {}) => OAuthService.slack({ scope: "emoji:read", ...extra }),
+ },
+ {
+ id: "zoom",
+ providerName: "Zoom",
+ description: "Connect your Zoom account",
+ make: (extra = {}) => OAuthService.zoom({ clientId: "cid", scope: "meeting:write", ...extra }),
+ },
];
- it.each(providers)("keeps the default providerId for $name and accepts an override", ({ name, make }) => {
- expect(clientOptions(make()).providerId).toBe(name);
- expect(clientOptions(make({ providerId: `${name}-second-account` })).providerId).toBe(`${name}-second-account`);
+ it.each(providers)("keeps the default PKCE client metadata for $id", ({ id, providerName, description, make }) => {
+ expect(clientOptions(make())).toMatchObject({ providerId: id, providerName, description });
});
- it.each(providers)("passes extraParameters through for $name without dropping provider defaults", ({ make }) => {
+ it.each(providers)("accepts custom PKCE client metadata for $id", ({ id, make }) => {
+ expect(
+ clientOptions(
+ make({
+ providerId: `${id}-second-account`,
+ providerName: "Work Account",
+ description: "Connect the work account",
+ }),
+ ),
+ ).toMatchObject({
+ providerId: `${id}-second-account`,
+ providerName: "Work Account",
+ description: "Connect the work account",
+ });
+ });
+
+ it.each(providers)(
+ "allows independent providerName and description overrides for $id",
+ ({ providerName, description, make }) => {
+ expect(clientOptions(make({ providerName: "Work Account" }))).toMatchObject({
+ providerName: "Work Account",
+ description,
+ });
+ expect(clientOptions(make({ description: "Connect the work account" }))).toMatchObject({
+ providerName,
+ description: "Connect the work account",
+ });
+ },
+ );
+
+ it.each(providers)("keeps the default providerId for $id and accepts an override", ({ id, make }) => {
+ expect(clientOptions(make()).providerId).toBe(id);
+ expect(clientOptions(make({ providerId: `${id}-second-account` })).providerId).toBe(`${id}-second-account`);
+ });
+
+ it.each(providers)("passes extraParameters through for $id without dropping provider defaults", ({ make }) => {
const service = make({ extraParameters: { custom_param: "x" } });
expect(service.extraParameters).toMatchObject({ custom_param: "x" });
});