@every-app/sdk 0.3.0-rc.9 → 0.3.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.
Files changed (131) hide show
  1. package/README.md +79 -74
  2. package/dist/cloudflare/catalogSettings.generated.d.ts +19 -6
  3. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
  4. package/dist/cloudflare/catalogSettings.generated.js +43 -14
  5. package/dist/cloudflare/index.d.ts +1 -1
  6. package/dist/cloudflare/index.d.ts.map +1 -1
  7. package/dist/cloudflare/worker.d.ts +63 -9
  8. package/dist/cloudflare/worker.d.ts.map +1 -1
  9. package/dist/cloudflare/worker.js +45 -9
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/internal/aiDefault.d.ts +36 -79
  13. package/dist/internal/aiDefault.d.ts.map +1 -1
  14. package/dist/internal/aiDefault.js +47 -149
  15. package/dist/internal/aiProviders.d.ts +1 -1
  16. package/dist/internal/aiProviders.d.ts.map +1 -1
  17. package/dist/internal/aiProviders.js +2 -2
  18. package/dist/internal/appSettings.d.ts +140 -89
  19. package/dist/internal/appSettings.d.ts.map +1 -1
  20. package/dist/internal/appSettings.js +264 -156
  21. package/dist/internal/capabilities.d.ts +109 -503
  22. package/dist/internal/capabilities.d.ts.map +1 -1
  23. package/dist/internal/capabilities.js +30 -550
  24. package/dist/internal/catalog/commerce.js +1 -1
  25. package/dist/internal/catalog/index.d.ts +35 -0
  26. package/dist/internal/catalog/index.d.ts.map +1 -1
  27. package/dist/internal/catalog/infra.d.ts +35 -0
  28. package/dist/internal/catalog/infra.d.ts.map +1 -1
  29. package/dist/internal/catalog/infra.js +65 -0
  30. package/dist/internal/devProviders.d.ts +1 -19
  31. package/dist/internal/devProviders.d.ts.map +1 -1
  32. package/dist/internal/devProviders.js +7 -1
  33. package/dist/internal/events.d.ts +52 -1
  34. package/dist/internal/events.d.ts.map +1 -1
  35. package/dist/internal/events.js +66 -4
  36. package/dist/internal/gatewaySettings.d.ts +6 -16
  37. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  38. package/dist/internal/gatewaySettings.js +5 -23
  39. package/dist/internal/index.d.ts +1 -0
  40. package/dist/internal/index.d.ts.map +1 -1
  41. package/dist/internal/index.js +1 -0
  42. package/dist/internal/mcpOAuth.d.ts +210 -0
  43. package/dist/internal/mcpOAuth.d.ts.map +1 -0
  44. package/dist/internal/mcpOAuth.js +735 -0
  45. package/dist/internal/oauthClient.d.ts +36 -44
  46. package/dist/internal/oauthClient.d.ts.map +1 -1
  47. package/dist/internal/oauthClient.js +30 -113
  48. package/dist/internal/oauthPresets.d.ts +356 -20
  49. package/dist/internal/oauthPresets.d.ts.map +1 -1
  50. package/dist/internal/oauthPresets.js +183 -861
  51. package/dist/internal/oauthScopes.d.ts +43 -155
  52. package/dist/internal/oauthScopes.d.ts.map +1 -1
  53. package/dist/internal/oauthScopes.js +82 -475
  54. package/dist/internal/oauthSpec.d.ts +28 -71
  55. package/dist/internal/oauthSpec.d.ts.map +1 -1
  56. package/dist/internal/oauthSpec.js +172 -448
  57. package/dist/internal/protocol.d.ts +24 -14
  58. package/dist/internal/protocol.d.ts.map +1 -1
  59. package/dist/internal/protocol.js +26 -7
  60. package/dist/internal/providerApps.d.ts +2 -2
  61. package/dist/internal/providerApps.js +2 -2
  62. package/dist/internal/providerRegistry.d.ts +5 -64
  63. package/dist/internal/providerRegistry.d.ts.map +1 -1
  64. package/dist/internal/providerRegistry.js +19 -118
  65. package/dist/internal/providerSpec.d.ts.map +1 -1
  66. package/dist/internal/providerSpec.js +14 -36
  67. package/dist/internal/remoteMcp.d.ts +191 -47
  68. package/dist/internal/remoteMcp.d.ts.map +1 -1
  69. package/dist/internal/remoteMcp.js +338 -133
  70. package/dist/internal/signInProviders.d.ts +1 -1
  71. package/dist/internal/signInProviders.d.ts.map +1 -1
  72. package/dist/internal/tools.d.ts +15 -8
  73. package/dist/internal/tools.d.ts.map +1 -1
  74. package/dist/internal/tools.js +9 -8
  75. package/dist/manifest.d.ts +96 -40
  76. package/dist/manifest.d.ts.map +1 -1
  77. package/dist/manifest.js +1 -1
  78. package/dist/server/ai.d.ts +1 -93
  79. package/dist/server/ai.d.ts.map +1 -1
  80. package/dist/server/ai.js +17 -111
  81. package/dist/server/attribution.d.ts +11 -1
  82. package/dist/server/attribution.d.ts.map +1 -1
  83. package/dist/server/attribution.js +7 -1
  84. package/dist/server/connectUrl.d.ts +5 -10
  85. package/dist/server/connectUrl.d.ts.map +1 -1
  86. package/dist/server/connectUrl.js +22 -14
  87. package/dist/server/context.d.ts +12 -14
  88. package/dist/server/context.d.ts.map +1 -1
  89. package/dist/server/context.js +23 -25
  90. package/dist/server/eventsEndpoint.d.ts +48 -0
  91. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  92. package/dist/server/eventsEndpoint.js +145 -0
  93. package/dist/server/everyApp.d.ts +16 -26
  94. package/dist/server/everyApp.d.ts.map +1 -1
  95. package/dist/server/everyApp.js +22 -46
  96. package/dist/server/gatewayFetch.d.ts +1 -1
  97. package/dist/server/gatewayFetch.js +1 -1
  98. package/dist/server/index.d.ts +6 -9
  99. package/dist/server/index.d.ts.map +1 -1
  100. package/dist/server/index.js +4 -8
  101. package/dist/server/integrationFetch.d.ts +66 -72
  102. package/dist/server/integrationFetch.d.ts.map +1 -1
  103. package/dist/server/integrationFetch.js +120 -392
  104. package/dist/server/refusal.d.ts +0 -9
  105. package/dist/server/refusal.d.ts.map +1 -1
  106. package/dist/server/refusal.js +0 -22
  107. package/dist/server/tools.d.ts +18 -7
  108. package/dist/server/tools.d.ts.map +1 -1
  109. package/dist/server/tools.js +15 -5
  110. package/dist/server/toolsEndpoint.d.ts +2 -1
  111. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  112. package/dist/server/toolsEndpoint.js +4 -3
  113. package/dist/server/webhooks.d.ts +56 -0
  114. package/dist/server/webhooks.d.ts.map +1 -0
  115. package/dist/server/webhooks.js +205 -0
  116. package/dist/testing/index.d.ts +2 -3
  117. package/dist/testing/index.d.ts.map +1 -1
  118. package/dist/testing/index.js +2 -6
  119. package/package.json +6 -1
  120. package/dist/server/aiBinding.d.ts +0 -68
  121. package/dist/server/aiBinding.d.ts.map +0 -1
  122. package/dist/server/aiBinding.js +0 -294
  123. package/dist/server/authorizeUrl.d.ts +0 -43
  124. package/dist/server/authorizeUrl.d.ts.map +0 -1
  125. package/dist/server/authorizeUrl.js +0 -106
  126. package/dist/server/emailBinding.d.ts +0 -72
  127. package/dist/server/emailBinding.d.ts.map +0 -1
  128. package/dist/server/emailBinding.js +0 -124
  129. package/dist/server/fromMcpTools.d.ts +0 -89
  130. package/dist/server/fromMcpTools.d.ts.map +0 -1
  131. package/dist/server/fromMcpTools.js +0 -192
@@ -1,30 +1,121 @@
1
- /**
2
- * OAuth presets (docs/design/2026-10-oauth-providers.md section 4): ready
3
- * `oauth2-user` specs, in the JSON shape `everyapp provider add` takes,
4
- * for the providers people most often want a "Connect" button for. A
5
- * preset becomes a `providers` row when added (`everyapp provider add
6
- * --oauth github`, the Admin dialog, `everyapp_provider_add` with
7
- * `preset`), so an operator may `provider show` and edit a copy; nothing
8
- * here is a hidden catalog entry, and no gateway reserves a preset's
9
- * setting names until it is added. Google, Microsoft and Slack are
10
- * catalog clients with a `flow` and are offered in the same picker. The
11
- * social networks' presets carry the tag `social`.
12
- *
13
- * Each preset parses with `parseProviderSpec` (oauthPresets.test.ts),
14
- * names a console where the client is made, the API hosts the token is
15
- * sent to, and scope sets an app declares under `scopes`. Scope strings
16
- * are the providers' own, verified against their docs on 2026-10-25;
17
- * the social presets' come from the catalog entries Postiz used, and
18
- * none of them has been tried against a live connect yet.
19
- */
1
+ import { providerBindingKey } from "./providerRegistry.js";
20
2
  import { parseProviderSpec, } from "./providerSpec.js";
21
- export const OAUTH_PRESETS = {
3
+ const PRESETS = {
4
+ // One Google Cloud project's OAuth client: Gmail, Calendar, Search
5
+ // Console, Analytics and any other Google API an app names a scope for.
6
+ "google-workspace": {
7
+ id: "google-workspace",
8
+ name: "Google",
9
+ description: "Gmail, Calendar, Drive, Search Console, Analytics and the other Google APIs, as the person who connects.",
10
+ docs: "https://console.cloud.google.com/apis/credentials",
11
+ api: "https://developers.google.com/identity/protocols/oauth2/scopes",
12
+ hosts: [
13
+ "gmail.googleapis.com",
14
+ "www.googleapis.com",
15
+ "searchconsole.googleapis.com",
16
+ "analyticsdata.googleapis.com",
17
+ "analyticsadmin.googleapis.com",
18
+ "openidconnect.googleapis.com",
19
+ ],
20
+ auth: {
21
+ kind: "oauth2-user",
22
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
23
+ tokenUrl: "https://oauth2.googleapis.com/token",
24
+ // A disconnect revokes the grant: POST form `token=` (RFC 7009), the
25
+ // refresh token once the access token has expired.
26
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
27
+ clientId: "GOOGLE_WORKSPACE_CLIENT_ID",
28
+ clientSecret: "GOOGLE_WORKSPACE_CLIENT_SECRET",
29
+ inject: "form",
30
+ pkce: true,
31
+ scopeSeparator: "space",
32
+ // An app writes `gmail.modify`; the authorize URL carries the full
33
+ // `https://www.googleapis.com/auth/gmail.modify`.
34
+ scopePrefix: "https://www.googleapis.com/auth/",
35
+ // A refresh token needs `access_type=offline`; `prompt=consent`
36
+ // makes Google issue one on every connect.
37
+ authorizeParams: { access_type: "offline", prompt: "consent" },
38
+ identity: {
39
+ url: "https://openidconnect.googleapis.com/v1/userinfo",
40
+ label: "email",
41
+ id: "sub",
42
+ scopes: ["openid", "email"],
43
+ },
44
+ // Google answers `email` and `profile` as the userinfo URLs, and may
45
+ // add `profile` beside `openid`.
46
+ tolerated: [
47
+ "profile",
48
+ "https://www.googleapis.com/auth/userinfo.email",
49
+ "https://www.googleapis.com/auth/userinfo.profile",
50
+ ],
51
+ },
52
+ hint: "A Web application OAuth client in Google Cloud, with the Gateway's callback as its redirect URI and the scopes your apps list on its consent screen.",
53
+ },
54
+ // The multi-tenant Entra endpoints: register the client for accounts in
55
+ // any organization and personal accounts.
56
+ "microsoft-365": {
57
+ id: "microsoft-365",
58
+ name: "Microsoft 365",
59
+ description: "Outlook mail and calendar, OneDrive and the rest of Microsoft Graph, as the person who connects.",
60
+ docs: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
61
+ api: "https://learn.microsoft.com/graph/permissions-reference",
62
+ hosts: ["graph.microsoft.com"],
63
+ auth: {
64
+ kind: "oauth2-user",
65
+ authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
66
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
67
+ clientId: "MICROSOFT_365_CLIENT_ID",
68
+ clientSecret: "MICROSOFT_365_CLIENT_SECRET",
69
+ inject: "form",
70
+ pkce: true,
71
+ scopeSeparator: "space",
72
+ // An app writes `Mail.Read`; Graph's scopes are fully qualified on
73
+ // the wire and may come back either way.
74
+ scopePrefix: "https://graph.microsoft.com/",
75
+ identity: {
76
+ url: "https://graph.microsoft.com/v1.0/me",
77
+ label: "userPrincipalName",
78
+ id: "id",
79
+ // `offline_access` is what makes Entra issue a refresh token.
80
+ scopes: ["User.Read", "offline_access"],
81
+ },
82
+ tolerated: ["openid", "profile", "email"],
83
+ },
84
+ hint: "An app registration in Entra for accounts in any organization and personal accounts, with the Gateway's callback as a Web redirect URI. If two apps share this client with different scopes, Microsoft may hand back both apps' scopes and the Gateway refuses the token: give each app its own client.",
85
+ },
86
+ // A Slack app's bot token: the top-level token of `oauth.v2.access`.
87
+ slack: {
88
+ id: "slack",
89
+ name: "Slack",
90
+ description: "Channels, members and messages, as the Slack app.",
91
+ docs: "https://api.slack.com/apps",
92
+ api: "https://api.slack.com/scopes",
93
+ hosts: ["slack.com"],
94
+ auth: {
95
+ kind: "oauth2-user",
96
+ authorizeUrl: "https://slack.com/oauth/v2/authorize",
97
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
98
+ revokeUrl: "https://slack.com/api/auth.revoke",
99
+ clientId: "SLACK_CLIENT_ID",
100
+ clientSecret: "SLACK_CLIENT_SECRET",
101
+ inject: "form",
102
+ // Slack refuses `code_challenge`.
103
+ pkce: false,
104
+ scopeSeparator: "comma",
105
+ identity: {
106
+ url: "https://slack.com/api/auth.test",
107
+ label: "team",
108
+ id: "team_id",
109
+ },
110
+ },
111
+ hint: "A Slack app with the Gateway's callback as its redirect URL and the bot scopes your apps list. The Gateway takes its Client ID and Client Secret, not a bot token (xoxb-).",
112
+ },
22
113
  "github-oauth": {
23
114
  id: "github-oauth",
24
115
  name: "GitHub",
25
116
  description: "Repositories, issues, pull requests and the person's profile.",
26
117
  docs: "https://github.com/settings/developers",
27
- api: "https://docs.github.com/rest",
118
+ api: "https://docs.github.com/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps",
28
119
  hosts: ["api.github.com"],
29
120
  auth: {
30
121
  kind: "oauth2-user",
@@ -36,38 +127,16 @@ export const OAUTH_PRESETS = {
36
127
  clientSecret: "GITHUB_OAUTH_CLIENT_SECRET",
37
128
  inject: "form",
38
129
  pkce: true,
39
- scopeDialect: "comma",
130
+ scopeSeparator: "comma",
40
131
  tokenHeaders: { Accept: "application/json" },
132
+ // GET /user answers with the public profile under any token.
41
133
  identity: {
42
134
  url: "https://api.github.com/user",
43
135
  label: "login",
44
136
  id: "id",
45
137
  },
46
138
  },
47
- scopeSets: {
48
- identity: {
49
- label: "GitHub account (who you are)",
50
- access: "read",
51
- scopes: ["read:user", "user:email"],
52
- },
53
- "repo.read": {
54
- label: "Repositories (read, private ones included)",
55
- access: "read",
56
- scopes: ["repo"],
57
- },
58
- "repo.write": {
59
- label: "Repositories (read and write)",
60
- access: "write",
61
- scopes: ["repo"],
62
- },
63
- "org.read": {
64
- label: "Organizations and teams (read)",
65
- access: "read",
66
- scopes: ["read:org"],
67
- },
68
- },
69
- access: ["read", "write"],
70
- hint: "Register an OAuth App (not a GitHub App) and paste its client id and secret. GitHub's `repo` scope is read and write; a read set with it still lets the Gateway refuse writes.",
139
+ hint: "Register an OAuth App (not a GitHub App) and paste its client id and secret. GitHub's `repo` scope is read and write.",
71
140
  },
72
141
  "notion-oauth": {
73
142
  id: "notion-oauth",
@@ -84,40 +153,10 @@ export const OAUTH_PRESETS = {
84
153
  clientSecret: "NOTION_OAUTH_CLIENT_SECRET",
85
154
  inject: "basic",
86
155
  pkce: false,
87
- scopeDialect: "space",
156
+ scopeSeparator: "space",
88
157
  authorizeParams: { owner: "user" },
89
158
  },
90
- // Notion reads with GET, and with POST for a search and a database
91
- // query; the read set lists those two, so a read grant never creates
92
- // or changes a page (POST /v1/pages, PATCH, DELETE need the write set).
93
- scopeSets: {
94
- "workspace.read": {
95
- label: "Shared pages and databases (read)",
96
- access: "read",
97
- scopes: [],
98
- routes: [
99
- {
100
- host: "api.notion.com",
101
- paths: [
102
- "/v1/users",
103
- "/v1/users/",
104
- "/v1/pages/",
105
- "/v1/databases/",
106
- "/v1/blocks/",
107
- "/v1/comments",
108
- ],
109
- posts: ["^/v1/search$", "^/v1/databases/[^/]+/query$"],
110
- },
111
- ],
112
- },
113
- "workspace.write": {
114
- label: "Shared pages and databases (read and write)",
115
- access: "write",
116
- scopes: [],
117
- },
118
- },
119
- access: ["read", "write"],
120
- hint: "Notion has no scopes: the person picks the pages to share on Notion's consent screen. Make the integration public to get a client id and secret. Apps send `Notion-Version` on every call.",
159
+ hint: "Notion has no scopes: an app lists scopes: [] and the person picks the pages to share on Notion's consent screen. Make the integration public to get a client id and secret. Apps send `Notion-Version` on every call.",
121
160
  },
122
161
  "linear-oauth": {
123
162
  id: "linear-oauth",
@@ -135,30 +174,16 @@ export const OAUTH_PRESETS = {
135
174
  clientSecret: "LINEAR_OAUTH_CLIENT_SECRET",
136
175
  inject: "form",
137
176
  pkce: false,
138
- scopeDialect: "comma",
139
- },
140
- // Linear's API is one GraphQL POST, which the Gateway can't tell a
141
- // query from a mutation on, so the one set is a write set: an app
142
- // lists Linear with access: "write", and Linear's own `read` scope is
143
- // what a read-only token holds.
144
- scopeSets: {
145
- "workspace.write": {
146
- label: "Issues, projects and teams (read and write)",
147
- access: "write",
148
- scopes: ["read", "write"],
149
- },
177
+ scopeSeparator: "comma",
150
178
  },
151
- access: ["write"],
152
- readMethods: [],
153
- writeMethods: ["POST"],
154
- hint: "Linear's API is GraphQL over POST, so every call needs access: \"write\"; a token's scopes decide what a query may change.",
179
+ hint: "Linear's scopes are read, write, issues:create and comments:create; its API is GraphQL over POST.",
155
180
  },
156
181
  "gitlab-oauth": {
157
182
  id: "gitlab-oauth",
158
183
  name: "GitLab",
159
184
  description: "Projects, merge requests and issues on gitlab.com.",
160
185
  docs: "https://gitlab.com/-/user_settings/applications",
161
- api: "https://docs.gitlab.com/api/rest/",
186
+ api: "https://docs.gitlab.com/integration/oauth_provider/#view-all-authorized-applications",
162
187
  hosts: ["gitlab.com"],
163
188
  auth: {
164
189
  kind: "oauth2-user",
@@ -169,31 +194,14 @@ export const OAUTH_PRESETS = {
169
194
  clientSecret: "GITLAB_OAUTH_CLIENT_SECRET",
170
195
  inject: "form",
171
196
  pkce: true,
172
- scopeDialect: "space",
197
+ scopeSeparator: "space",
173
198
  identity: {
174
199
  url: "https://gitlab.com/api/v4/user",
175
200
  label: "username",
176
201
  id: "id",
177
- },
178
- },
179
- scopeSets: {
180
- identity: {
181
- label: "GitLab account (who you are)",
182
- access: "read",
183
202
  scopes: ["read_user"],
184
203
  },
185
- "api.read": {
186
- label: "Projects, issues and merge requests (read)",
187
- access: "read",
188
- scopes: ["read_api"],
189
- },
190
- "api.write": {
191
- label: "Projects, issues and merge requests (read and write)",
192
- access: "write",
193
- scopes: ["api"],
194
- },
195
204
  },
196
- access: ["read", "write"],
197
205
  hint: "A self-managed GitLab needs a custom provider with its own host in place of gitlab.com.",
198
206
  },
199
207
  atlassian: {
@@ -201,7 +209,7 @@ export const OAUTH_PRESETS = {
201
209
  name: "Atlassian (Jira and Confluence)",
202
210
  description: "Jira issues and Confluence pages on the sites the person grants.",
203
211
  docs: "https://developer.atlassian.com/console/myapps/",
204
- api: "https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/",
212
+ api: "https://developer.atlassian.com/cloud/jira/platform/scopes-for-oauth-2-3LO-and-forge-apps/",
205
213
  hosts: ["api.atlassian.com"],
206
214
  auth: {
207
215
  kind: "oauth2-user",
@@ -211,41 +219,16 @@ export const OAUTH_PRESETS = {
211
219
  clientSecret: "ATLASSIAN_CLIENT_SECRET",
212
220
  inject: "form",
213
221
  pkce: false,
214
- scopeDialect: "space",
222
+ scopeSeparator: "space",
215
223
  authorizeParams: { audience: "api.atlassian.com", prompt: "consent" },
216
224
  identity: {
217
225
  url: "https://api.atlassian.com/me",
218
226
  label: "email",
219
227
  id: "account_id",
228
+ // `offline_access` is what yields a refresh token.
229
+ scopes: ["read:me", "offline_access"],
220
230
  },
221
231
  },
222
- scopeSets: {
223
- identity: {
224
- label: "Atlassian account (who you are)",
225
- access: "read",
226
- scopes: ["read:me"],
227
- },
228
- "jira.read": {
229
- label: "Jira issues and projects (read)",
230
- access: "read",
231
- scopes: ["read:jira-work", "read:jira-user"],
232
- },
233
- "jira.write": {
234
- label: "Jira issues (read and write)",
235
- access: "write",
236
- scopes: ["read:jira-work", "write:jira-work"],
237
- },
238
- "confluence.read": {
239
- label: "Confluence pages (read)",
240
- access: "read",
241
- scopes: [
242
- "read:confluence-content.all",
243
- "read:confluence-space.summary",
244
- ],
245
- },
246
- },
247
- alwaysRequested: ["offline_access"],
248
- access: ["read", "write"],
249
232
  hint: "Calls go to api.atlassian.com/ex/jira/<cloud id>/… ; the cloud id comes from /oauth/token/accessible-resources.",
250
233
  },
251
234
  "hubspot-oauth": {
@@ -253,7 +236,7 @@ export const OAUTH_PRESETS = {
253
236
  name: "HubSpot",
254
237
  description: "CRM contacts, companies and deals.",
255
238
  docs: "https://developers.hubspot.com/",
256
- api: "https://developers.hubspot.com/docs/reference/api",
239
+ api: "https://developers.hubspot.com/docs/api/working-with-oauth",
257
240
  hosts: ["api.hubapi.com"],
258
241
  auth: {
259
242
  kind: "oauth2-user",
@@ -263,39 +246,16 @@ export const OAUTH_PRESETS = {
263
246
  clientSecret: "HUBSPOT_OAUTH_CLIENT_SECRET",
264
247
  inject: "form",
265
248
  pkce: false,
266
- scopeDialect: "space",
267
- },
268
- scopeSets: {
269
- "contacts.read": {
270
- label: "Contacts (read)",
271
- access: "read",
272
- scopes: ["crm.objects.contacts.read"],
273
- },
274
- "contacts.write": {
275
- label: "Contacts (read and write)",
276
- access: "write",
277
- scopes: ["crm.objects.contacts.read", "crm.objects.contacts.write"],
278
- },
279
- "companies.read": {
280
- label: "Companies (read)",
281
- access: "read",
282
- scopes: ["crm.objects.companies.read"],
283
- },
284
- "deals.read": {
285
- label: "Deals (read)",
286
- access: "read",
287
- scopes: ["crm.objects.deals.read"],
288
- },
249
+ scopeSeparator: "space",
289
250
  },
290
- access: ["read", "write"],
291
- hint: "The app's scopes in HubSpot's developer console must match the sets apps declare, or HubSpot refuses the authorize URL.",
251
+ hint: "The app's scopes in HubSpot's developer console must include the scopes apps list, or HubSpot refuses the authorize URL.",
292
252
  },
293
253
  zoom: {
294
254
  id: "zoom",
295
255
  name: "Zoom",
296
256
  description: "The person's meetings and recordings.",
297
257
  docs: "https://marketplace.zoom.us/develop/create",
298
- api: "https://developers.zoom.us/docs/api/",
258
+ api: "https://developers.zoom.us/docs/integrations/oauth-scopes-overview/",
299
259
  hosts: ["api.zoom.us"],
300
260
  auth: {
301
261
  kind: "oauth2-user",
@@ -306,48 +266,22 @@ export const OAUTH_PRESETS = {
306
266
  clientSecret: "ZOOM_CLIENT_SECRET",
307
267
  inject: "basic",
308
268
  pkce: true,
309
- scopeDialect: "space",
269
+ scopeSeparator: "space",
310
270
  identity: {
311
271
  url: "https://api.zoom.us/v2/users/me",
312
272
  label: "email",
313
273
  id: "id",
314
- },
315
- },
316
- scopeSets: {
317
- identity: {
318
- label: "Zoom account (who you are)",
319
- access: "read",
320
274
  scopes: ["user:read:user"],
321
275
  },
322
- "meetings.read": {
323
- label: "Meetings (read)",
324
- access: "read",
325
- scopes: ["meeting:read:meeting", "meeting:read:list_meetings"],
326
- },
327
- "meetings.write": {
328
- label: "Meetings (read, create and change)",
329
- access: "write",
330
- scopes: [
331
- "meeting:read:meeting",
332
- "meeting:write:meeting",
333
- "meeting:update:meeting",
334
- ],
335
- },
336
- "recordings.read": {
337
- label: "Cloud recordings (read)",
338
- access: "read",
339
- scopes: ["cloud_recording:read:list_user_recordings"],
340
- },
341
276
  },
342
- access: ["read", "write"],
343
- hint: "A General app with user-managed OAuth; Zoom's granular scope names are the ones listed here.",
277
+ hint: "A General app with user-managed OAuth, using Zoom's granular scope names.",
344
278
  },
345
279
  dropbox: {
346
280
  id: "dropbox",
347
281
  name: "Dropbox",
348
282
  description: "Files and folders in the person's Dropbox.",
349
283
  docs: "https://www.dropbox.com/developers/apps",
350
- api: "https://www.dropbox.com/developers/documentation/http/documentation",
284
+ api: "https://developers.dropbox.com/oauth-guide",
351
285
  hosts: ["api.dropboxapi.com", "content.dropboxapi.com"],
352
286
  auth: {
353
287
  kind: "oauth2-user",
@@ -358,76 +292,19 @@ export const OAUTH_PRESETS = {
358
292
  clientSecret: "DROPBOX_OAUTH_CLIENT_SECRET",
359
293
  inject: "form",
360
294
  pkce: true,
361
- scopeDialect: "space",
295
+ scopeSeparator: "space",
362
296
  authorizeParams: { token_access_type: "offline" },
297
+ // Dropbox grants account_info.read to every app.
298
+ tolerated: ["account_info.read"],
363
299
  },
364
- // Dropbox is POST throughout, so each read set lists the read calls
365
- // it unlocks under `posts`; everything else needs the write set.
366
- scopeSets: {
367
- identity: {
368
- label: "Dropbox account (who you are)",
369
- access: "read",
370
- scopes: ["account_info.read"],
371
- routes: [
372
- {
373
- host: "api.dropboxapi.com",
374
- paths: [],
375
- posts: [
376
- "^/2/users/get_current_account$",
377
- "^/2/users/get_space_usage$",
378
- ],
379
- },
380
- ],
381
- },
382
- "files.read": {
383
- label: "Files and folders (read)",
384
- access: "read",
385
- scopes: ["files.metadata.read", "files.content.read"],
386
- routes: [
387
- {
388
- host: "api.dropboxapi.com",
389
- paths: [],
390
- posts: [
391
- "^/2/files/list_folder$",
392
- "^/2/files/list_folder/continue$",
393
- "^/2/files/get_metadata$",
394
- "^/2/files/search_v2$",
395
- "^/2/files/search/continue_v2$",
396
- "^/2/files/get_temporary_link$",
397
- "^/2/files/list_revisions$",
398
- ],
399
- },
400
- {
401
- host: "content.dropboxapi.com",
402
- paths: [],
403
- posts: [
404
- "^/2/files/download$",
405
- "^/2/files/get_thumbnail_v2$",
406
- "^/2/files/export$",
407
- ],
408
- },
409
- ],
410
- },
411
- "files.write": {
412
- label: "Files and folders (read and write)",
413
- access: "write",
414
- scopes: [
415
- "files.metadata.read",
416
- "files.content.read",
417
- "files.metadata.write",
418
- "files.content.write",
419
- ],
420
- },
421
- },
422
- access: ["read", "write"],
423
- hint: 'Dropbox\'s API is POST throughout: a read set unlocks its listed read calls, anything else needs access: "write". `token_access_type=offline` is what yields a refresh token.',
300
+ hint: "`token_access_type=offline` is what yields a refresh token.",
424
301
  },
425
302
  spotify: {
426
303
  id: "spotify",
427
304
  name: "Spotify",
428
305
  description: "The person's library, playlists and listening.",
429
306
  docs: "https://developer.spotify.com/dashboard",
430
- api: "https://developer.spotify.com/documentation/web-api",
307
+ api: "https://developer.spotify.com/documentation/web-api/concepts/scopes",
431
308
  hosts: ["api.spotify.com"],
432
309
  auth: {
433
310
  kind: "oauth2-user",
@@ -437,612 +314,28 @@ export const OAUTH_PRESETS = {
437
314
  clientSecret: "SPOTIFY_CLIENT_SECRET",
438
315
  inject: "basic",
439
316
  pkce: true,
440
- scopeDialect: "space",
317
+ scopeSeparator: "space",
318
+ // GET /v1/me names the account under any token.
441
319
  identity: {
442
320
  url: "https://api.spotify.com/v1/me",
443
321
  label: "display_name",
444
322
  id: "id",
445
323
  },
446
324
  },
447
- scopeSets: {
448
- identity: {
449
- label: "Spotify account (who you are)",
450
- access: "read",
451
- scopes: ["user-read-email", "user-read-private"],
452
- },
453
- "library.read": {
454
- label: "Saved tracks and albums (read)",
455
- access: "read",
456
- scopes: ["user-library-read"],
457
- },
458
- "playlists.read": {
459
- label: "Playlists (read)",
460
- access: "read",
461
- scopes: ["playlist-read-private", "playlist-read-collaborative"],
462
- },
463
- "playlists.write": {
464
- label: "Playlists (read and change)",
465
- access: "write",
466
- scopes: [
467
- "playlist-read-private",
468
- "playlist-modify-private",
469
- "playlist-modify-public",
470
- ],
471
- },
472
- },
473
- access: ["read", "write"],
474
- },
475
- "x-oauth": {
476
- id: "x-oauth",
477
- name: "X (OAuth 2)",
478
- description: "Posts and the person's profile on X, over the v2 API.",
479
- docs: "https://developer.x.com/en/portal/dashboard",
480
- api: "https://docs.x.com/x-api",
481
- hosts: ["api.x.com"],
482
- auth: {
483
- kind: "oauth2-user",
484
- authorizeUrl: "https://x.com/i/oauth2/authorize",
485
- tokenUrl: "https://api.x.com/2/oauth2/token",
486
- revokeUrl: "https://api.x.com/2/oauth2/revoke",
487
- clientId: "X_OAUTH_CLIENT_ID",
488
- clientSecret: "X_OAUTH_CLIENT_SECRET",
489
- inject: "basic",
490
- pkce: true,
491
- scopeDialect: "space",
492
- identity: {
493
- url: "https://api.x.com/2/users/me",
494
- label: "data.username",
495
- id: "data.id",
496
- },
497
- },
498
- scopeSets: {
499
- identity: {
500
- label: "X account (who you are)",
501
- access: "read",
502
- scopes: ["users.read", "tweet.read"],
503
- },
504
- "posts.read": {
505
- label: "Posts and timelines (read)",
506
- access: "read",
507
- scopes: ["tweet.read", "users.read"],
508
- },
509
- "posts.write": {
510
- label: "Posts (read and publish)",
511
- access: "write",
512
- scopes: ["tweet.read", "users.read", "tweet.write"],
513
- },
514
- },
515
- alwaysRequested: ["offline.access"],
516
- access: ["read", "write"],
517
- hint: "OAuth 2 with PKCE on X's v2 API; X's OAuth 1.0a (the v1.1 media upload) stays an app secret.",
518
- },
519
- // Social networks, tagged `social`: the clients Postiz connected its
520
- // channels with, once catalog entries every gateway reserved names for.
521
- linkedin: {
522
- id: "linkedin",
523
- name: "LinkedIn",
524
- description: "Posts on a member's profile and on the company pages they admin.",
525
- docs: "https://www.linkedin.com/developers/apps",
526
- api: "https://learn.microsoft.com/linkedin/",
527
- hosts: ["api.linkedin.com"],
528
- auth: {
529
- kind: "oauth2-user",
530
- authorizeUrl: "https://www.linkedin.com/oauth/v2/authorization",
531
- tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
532
- clientId: "LINKEDIN_CLIENT_ID",
533
- clientSecret: "LINKEDIN_CLIENT_SECRET",
534
- inject: "form",
535
- // PKCE is for LinkedIn's native apps; a web client sends the secret.
536
- pkce: false,
537
- scopeDialect: "space",
538
- identity: {
539
- url: "https://api.linkedin.com/v2/userinfo",
540
- label: "name",
541
- id: "sub",
542
- },
543
- },
544
- scopeSets: {
545
- identity: {
546
- label: "LinkedIn account (who you are)",
547
- access: "read",
548
- scopes: ["openid", "profile"],
549
- },
550
- "posts.write": {
551
- label: "Posts on your profile",
552
- access: "write",
553
- scopes: ["openid", "profile", "w_member_social"],
554
- },
555
- "pages.write": {
556
- label: "Company pages you admin (read and post)",
557
- access: "write",
558
- scopes: [
559
- "rw_organization_admin",
560
- "w_organization_social",
561
- "r_organization_social",
562
- ],
563
- },
564
- },
565
- access: ["read", "write"],
566
- tags: ["social"],
567
- hint: "Add the Sign In with LinkedIn and Share on LinkedIn products to the app; company pages need the Community Management API. Refresh tokens come to approved partners only. Not yet tried against a live connect.",
568
- },
569
- facebook: {
570
- id: "facebook",
571
- name: "Facebook Pages",
572
- description: "Facebook pages, and the Instagram business accounts linked to them.",
573
- docs: "https://developers.facebook.com/apps",
574
- api: "https://developers.facebook.com/docs/graph-api",
575
- hosts: ["graph.facebook.com"],
576
- auth: {
577
- kind: "oauth2-user",
578
- authorizeUrl: "https://www.facebook.com/v25.0/dialog/oauth",
579
- tokenUrl: "https://graph.facebook.com/v25.0/oauth/access_token",
580
- clientId: "FACEBOOK_APP_ID",
581
- clientSecret: "FACEBOOK_APP_SECRET",
582
- inject: "form",
583
- pkce: false,
584
- scopeDialect: "comma",
585
- identity: {
586
- url: "https://graph.facebook.com/v25.0/me",
587
- label: "name",
588
- id: "id",
589
- },
590
- },
591
- scopeSets: {
592
- identity: {
593
- label: "Facebook account (who you are)",
594
- access: "read",
595
- scopes: ["public_profile"],
596
- },
597
- "pages.read": {
598
- label: "Pages you manage and their insights (read)",
599
- access: "read",
600
- scopes: ["pages_show_list", "pages_read_engagement", "read_insights"],
601
- },
602
- "pages.write": {
603
- label: "Pages you manage (read, post and reply)",
604
- access: "write",
605
- scopes: [
606
- "pages_show_list",
607
- "business_management",
608
- "pages_manage_posts",
609
- "pages_manage_engagement",
610
- "pages_read_engagement",
611
- ],
612
- },
613
- "instagram.write": {
614
- label: "Instagram business accounts linked to your pages",
615
- access: "write",
616
- scopes: [
617
- "pages_show_list",
618
- "instagram_basic",
619
- "instagram_content_publish",
620
- "instagram_manage_comments",
621
- "instagram_manage_insights",
622
- ],
623
- },
624
- },
625
- access: ["read", "write"],
626
- tags: ["social"],
627
- hint: "A Business app with Facebook Login. Meta issues no refresh token: a person reconnects when the token expires. Not yet tried against a live connect.",
628
- },
629
- instagram: {
630
- id: "instagram",
631
- name: "Instagram",
632
- description: "An Instagram professional account on its own, without a Facebook page.",
633
- docs: "https://developers.facebook.com/apps",
634
- api: "https://developers.facebook.com/docs/instagram-platform",
635
- hosts: ["graph.instagram.com"],
636
- auth: {
637
- kind: "oauth2-user",
638
- authorizeUrl: "https://www.instagram.com/oauth/authorize",
639
- tokenUrl: "https://api.instagram.com/oauth/access_token",
640
- clientId: "INSTAGRAM_APP_ID",
641
- clientSecret: "INSTAGRAM_APP_SECRET",
642
- inject: "form",
643
- pkce: false,
644
- scopeDialect: "comma",
645
- identity: {
646
- url: "https://graph.instagram.com/me?fields=username",
647
- label: "username",
648
- id: "id",
649
- },
650
- },
651
- scopeSets: {
652
- identity: {
653
- label: "Instagram account (who you are)",
654
- access: "read",
655
- scopes: ["instagram_business_basic"],
656
- },
657
- "insights.read": {
658
- label: "Insights (read)",
659
- access: "read",
660
- scopes: [
661
- "instagram_business_basic",
662
- "instagram_business_manage_insights",
663
- ],
664
- },
665
- "posts.write": {
666
- label: "Posts and comments",
667
- access: "write",
668
- scopes: [
669
- "instagram_business_basic",
670
- "instagram_business_content_publish",
671
- "instagram_business_manage_comments",
672
- ],
673
- },
674
- },
675
- access: ["read", "write"],
676
- tags: ["social"],
677
- hint: "The Instagram API with Instagram Login, on a Meta app. The token lasts an hour; the long-lived exchange isn't done by the Gateway. Not yet tried against a live connect.",
678
- },
679
- threads: {
680
- id: "threads",
681
- name: "Threads",
682
- description: "Posts, replies and insights on a Threads profile.",
683
- docs: "https://developers.facebook.com/apps",
684
- api: "https://developers.facebook.com/docs/threads",
685
- hosts: ["graph.threads.net"],
686
- auth: {
687
- kind: "oauth2-user",
688
- authorizeUrl: "https://threads.net/oauth/authorize",
689
- tokenUrl: "https://graph.threads.net/oauth/access_token",
690
- clientId: "THREADS_APP_ID",
691
- clientSecret: "THREADS_APP_SECRET",
692
- inject: "form",
693
- pkce: false,
694
- scopeDialect: "comma",
695
- identity: {
696
- url: "https://graph.threads.net/v1.0/me?fields=id,username",
697
- label: "username",
698
- id: "id",
699
- },
700
- },
701
- scopeSets: {
702
- identity: {
703
- label: "Threads profile (who you are)",
704
- access: "read",
705
- scopes: ["threads_basic"],
706
- },
707
- "insights.read": {
708
- label: "Insights (read)",
709
- access: "read",
710
- scopes: ["threads_basic", "threads_manage_insights"],
711
- },
712
- "posts.write": {
713
- label: "Posts and replies",
714
- access: "write",
715
- scopes: [
716
- "threads_basic",
717
- "threads_content_publish",
718
- "threads_manage_replies",
719
- ],
720
- },
721
- },
722
- access: ["read", "write"],
723
- tags: ["social"],
724
- hint: "The Threads use case on a Meta app. The token lasts an hour; the long-lived exchange isn't done by the Gateway. Not yet tried against a live connect.",
725
- },
726
- youtube: {
727
- id: "youtube",
728
- name: "YouTube",
729
- description: "YouTube channels and their analytics, and Google Business Profiles.",
730
- docs: "https://console.cloud.google.com/apis/credentials",
731
- api: "https://developers.google.com/youtube/v3",
732
- hosts: [
733
- "www.googleapis.com",
734
- "youtubeanalytics.googleapis.com",
735
- "mybusinessaccountmanagement.googleapis.com",
736
- "mybusinessbusinessinformation.googleapis.com",
737
- "mybusiness.googleapis.com",
738
- "businessprofileperformance.googleapis.com",
739
- "openidconnect.googleapis.com",
740
- ],
741
- auth: {
742
- kind: "oauth2-user",
743
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
744
- tokenUrl: "https://oauth2.googleapis.com/token",
745
- revokeUrl: "https://oauth2.googleapis.com/revoke",
746
- clientId: "YOUTUBE_CLIENT_ID",
747
- clientSecret: "YOUTUBE_CLIENT_SECRET",
748
- inject: "form",
749
- pkce: true,
750
- scopeDialect: "google",
751
- authorizeParams: { access_type: "offline", prompt: "consent" },
752
- identity: {
753
- url: "https://openidconnect.googleapis.com/v1/userinfo",
754
- label: "email",
755
- id: "sub",
756
- },
757
- },
758
- scopeSets: {
759
- identity: {
760
- label: "Google account (who you are)",
761
- access: "read",
762
- scopes: [
763
- "https://www.googleapis.com/auth/userinfo.profile",
764
- "https://www.googleapis.com/auth/userinfo.email",
765
- ],
766
- },
767
- "youtube.read": {
768
- label: "YouTube channels and analytics (read)",
769
- access: "read",
770
- scopes: [
771
- "https://www.googleapis.com/auth/youtube.readonly",
772
- "https://www.googleapis.com/auth/yt-analytics.readonly",
773
- ],
774
- },
775
- "youtube.write": {
776
- label: "YouTube channels (upload and manage)",
777
- access: "write",
778
- scopes: [
779
- "https://www.googleapis.com/auth/youtube",
780
- "https://www.googleapis.com/auth/youtube.upload",
781
- "https://www.googleapis.com/auth/youtube.force-ssl",
782
- ],
783
- },
784
- "business.write": {
785
- label: "Google Business Profiles (read and post)",
786
- access: "write",
787
- scopes: ["https://www.googleapis.com/auth/business.manage"],
788
- },
789
- },
790
- access: ["read", "write"],
791
- tags: ["social"],
792
- hint: "Its own Google Cloud project, apart from google-workspace's: YouTube's scopes need their own verification. Not yet tried against a live connect.",
793
- },
794
- reddit: {
795
- id: "reddit",
796
- name: "Reddit",
797
- description: "Reading subreddits and posting as the person.",
798
- docs: "https://www.reddit.com/prefs/apps",
799
- api: "https://www.reddit.com/dev/api",
800
- hosts: ["oauth.reddit.com"],
801
- auth: {
802
- kind: "oauth2-user",
803
- authorizeUrl: "https://www.reddit.com/api/v1/authorize",
804
- tokenUrl: "https://www.reddit.com/api/v1/access_token",
805
- clientId: "REDDIT_CLIENT_ID",
806
- clientSecret: "REDDIT_CLIENT_SECRET",
807
- inject: "basic",
808
- pkce: false,
809
- scopeDialect: "space",
810
- // A refresh token only with a permanent grant.
811
- authorizeParams: { duration: "permanent" },
812
- identity: {
813
- url: "https://oauth.reddit.com/api/v1/me",
814
- label: "name",
815
- id: "id",
816
- },
817
- },
818
- scopeSets: {
819
- identity: {
820
- label: "Reddit account (who you are)",
821
- access: "read",
822
- scopes: ["identity"],
823
- },
824
- "posts.read": {
825
- label: "Subreddits and posts (read)",
826
- access: "read",
827
- scopes: ["identity", "read"],
828
- },
829
- "posts.write": {
830
- label: "Posts (read, submit and flair)",
831
- access: "write",
832
- scopes: ["identity", "read", "submit", "flair"],
833
- },
834
- },
835
- access: ["read", "write"],
836
- tags: ["social"],
837
- hint: "A web app on Reddit's apps page. Not yet tried against a live connect.",
838
- },
839
- pinterest: {
840
- id: "pinterest",
841
- name: "Pinterest",
842
- description: "Boards and pins on a Pinterest account.",
843
- docs: "https://developers.pinterest.com/apps/",
844
- api: "https://developers.pinterest.com/docs/api/v5/",
845
- hosts: ["api.pinterest.com"],
846
- auth: {
847
- kind: "oauth2-user",
848
- authorizeUrl: "https://www.pinterest.com/oauth/",
849
- tokenUrl: "https://api.pinterest.com/v5/oauth/token",
850
- clientId: "PINTEREST_CLIENT_ID",
851
- clientSecret: "PINTEREST_CLIENT_SECRET",
852
- inject: "basic",
853
- pkce: false,
854
- scopeDialect: "comma",
855
- identity: {
856
- url: "https://api.pinterest.com/v5/user_account",
857
- label: "username",
858
- id: "id",
859
- },
860
- },
861
- scopeSets: {
862
- identity: {
863
- label: "Pinterest account (who you are)",
864
- access: "read",
865
- scopes: ["user_accounts:read"],
866
- },
867
- "pins.read": {
868
- label: "Boards and pins (read)",
869
- access: "read",
870
- scopes: ["user_accounts:read", "boards:read", "pins:read"],
871
- },
872
- "pins.write": {
873
- label: "Boards and pins (read and write)",
874
- access: "write",
875
- scopes: [
876
- "user_accounts:read",
877
- "boards:read",
878
- "boards:write",
879
- "pins:read",
880
- "pins:write",
881
- ],
882
- },
883
- },
884
- access: ["read", "write"],
885
- tags: ["social"],
886
- hint: "Not yet tried against a live connect.",
887
- },
888
- discord: {
889
- id: "discord",
890
- name: "Discord",
891
- description: "The person's Discord account and servers, and installing a bot in a server.",
892
- docs: "https://discord.com/developers/applications",
893
- api: "https://discord.com/developers/docs/reference",
894
- hosts: ["discord.com"],
895
- auth: {
896
- kind: "oauth2-user",
897
- authorizeUrl: "https://discord.com/oauth2/authorize",
898
- tokenUrl: "https://discord.com/api/oauth2/token",
899
- revokeUrl: "https://discord.com/api/oauth2/token/revoke",
900
- clientId: "DISCORD_CLIENT_ID",
901
- clientSecret: "DISCORD_CLIENT_SECRET",
902
- inject: "basic",
903
- pkce: true,
904
- scopeDialect: "space",
905
- identity: {
906
- url: "https://discord.com/api/users/@me",
907
- label: "username",
908
- id: "id",
909
- },
910
- },
911
- scopeSets: {
912
- identity: {
913
- label: "Discord account (who you are)",
914
- access: "read",
915
- scopes: ["identify"],
916
- },
917
- "servers.read": {
918
- label: "Servers you are in (read)",
919
- access: "read",
920
- scopes: ["identify", "guilds"],
921
- },
922
- "bot.write": {
923
- label: "Add the bot to a server",
924
- access: "write",
925
- scopes: ["identify", "guilds", "bot"],
926
- },
927
- },
928
- access: ["read", "write"],
929
- tags: ["social"],
930
- hint: "Posting as the bot uses the bot's own token, an app secret the Gateway doesn't hold. Not yet tried against a live connect.",
931
- },
932
- kick: {
933
- id: "kick",
934
- name: "Kick",
935
- description: "A Kick channel and its chat.",
936
- docs: "https://kick.com/settings/developer",
937
- api: "https://docs.kick.com/",
938
- hosts: ["api.kick.com"],
939
- auth: {
940
- kind: "oauth2-user",
941
- authorizeUrl: "https://id.kick.com/oauth/authorize",
942
- tokenUrl: "https://id.kick.com/oauth/token",
943
- clientId: "KICK_CLIENT_ID",
944
- clientSecret: "KICK_CLIENT_SECRET",
945
- inject: "form",
946
- pkce: true,
947
- scopeDialect: "space",
948
- },
949
- scopeSets: {
950
- identity: {
951
- label: "Kick account (who you are)",
952
- access: "read",
953
- scopes: ["user:read"],
954
- },
955
- "channel.read": {
956
- label: "Your channel (read)",
957
- access: "read",
958
- scopes: ["user:read", "channel:read"],
959
- },
960
- "chat.write": {
961
- label: "Chat messages",
962
- access: "write",
963
- scopes: ["user:read", "chat:write"],
964
- },
965
- },
966
- access: ["read", "write"],
967
- tags: ["social"],
968
- hint: "Not yet tried against a live connect.",
969
- },
970
- twitch: {
971
- id: "twitch",
972
- name: "Twitch",
973
- description: "A Twitch channel's chat and announcements.",
974
- docs: "https://dev.twitch.tv/console/apps",
975
- api: "https://dev.twitch.tv/docs/api/",
976
- hosts: ["api.twitch.tv"],
977
- auth: {
978
- kind: "oauth2-user",
979
- authorizeUrl: "https://id.twitch.tv/oauth2/authorize",
980
- tokenUrl: "https://id.twitch.tv/oauth2/token",
981
- clientId: "TWITCH_CLIENT_ID",
982
- clientSecret: "TWITCH_CLIENT_SECRET",
983
- inject: "form",
984
- pkce: false,
985
- scopeDialect: "space",
986
- },
987
- scopeSets: {
988
- "chat.read": {
989
- label: "Chat (read)",
990
- access: "read",
991
- scopes: ["user:read:chat"],
992
- },
993
- "chat.write": {
994
- label: "Chat messages and announcements",
995
- access: "write",
996
- scopes: [
997
- "user:read:chat",
998
- "user:write:chat",
999
- "moderator:manage:announcements",
1000
- ],
1001
- },
1002
- },
1003
- access: ["read", "write"],
1004
- tags: ["social"],
1005
- hint: "Helix also wants the public client id as a Client-Id header on every call. Not yet tried against a live connect.",
1006
- },
1007
- tumblr: {
1008
- id: "tumblr",
1009
- name: "Tumblr",
1010
- description: "Posting to the person's Tumblr blogs.",
1011
- docs: "https://www.tumblr.com/oauth/apps",
1012
- api: "https://www.tumblr.com/docs/en/api/v2",
1013
- hosts: ["api.tumblr.com"],
1014
- auth: {
1015
- kind: "oauth2-user",
1016
- authorizeUrl: "https://www.tumblr.com/oauth2/authorize",
1017
- tokenUrl: "https://api.tumblr.com/v2/oauth2/token",
1018
- clientId: "TUMBLR_CLIENT_ID",
1019
- clientSecret: "TUMBLR_CLIENT_SECRET",
1020
- inject: "form",
1021
- pkce: false,
1022
- scopeDialect: "space",
1023
- identity: {
1024
- url: "https://api.tumblr.com/v2/user/info",
1025
- label: "response.user.name",
1026
- },
1027
- },
1028
- scopeSets: {
1029
- identity: {
1030
- label: "Tumblr account (who you are)",
1031
- access: "read",
1032
- scopes: ["basic"],
1033
- },
1034
- "posts.write": {
1035
- label: "Posts on your blogs",
1036
- access: "write",
1037
- scopes: ["basic", "write"],
1038
- },
1039
- },
1040
- alwaysRequested: ["offline_access"],
1041
- access: ["read", "write"],
1042
- tags: ["social"],
1043
- hint: "Not yet tried against a live connect.",
1044
325
  },
1045
326
  };
327
+ /** Every preset by id, in the order the picker shows them. */
328
+ export const OAUTH_PRESETS = PRESETS;
329
+ /** The agent docs' setup guides for a preset's client, where one exists. */
330
+ const SETUP_GUIDES = {
331
+ "google-workspace": "setup/google.md",
332
+ "microsoft-365": "setup/microsoft-365.md",
333
+ slack: "setup/slack.md",
334
+ };
335
+ /** The setup guide for OAuth provider `id`'s client, when its preset has one (`setup` in an app's settings report). */
336
+ export function oauthSetupGuide(id) {
337
+ return SETUP_GUIDES[id];
338
+ }
1046
339
  /** The preset ids, in the order the picker shows them. */
1047
340
  export function oauthPresetIds() {
1048
341
  return Object.keys(OAUTH_PRESETS);
@@ -1058,7 +351,36 @@ export function oauthPreset(id) {
1058
351
  }
1059
352
  return parsed.capability;
1060
353
  }
1061
- /** Whether `capability` is an OAuth provider the Gateway runs the flow for. */
1062
- export function isOAuthProvider(capability) {
1063
- return capability?.mode === "oauth_client" && capability.flow !== undefined;
354
+ /**
355
+ * Whether `capability`, a Gateway's row for `id`, is that preset's: its
356
+ * client would go to the preset's endpoints and hosts under the preset's
357
+ * setting names (the same binding key). Adding the preset again takes the
358
+ * client for such a row; a row of another spec under a preset's id is the
359
+ * operator's own and is never taken for the preset.
360
+ */
361
+ export function isPresetRow(id, capability) {
362
+ const preset = oauthPreset(id);
363
+ return (preset !== null &&
364
+ providerBindingKey(capability) === providerBindingKey(preset));
365
+ }
366
+ /**
367
+ * The preset ids among `ids` (an app's capabilities) that `lookup` (a
368
+ * Gateway's registry) doesn't have: the rows a deploy or an install
369
+ * writes from their presets before it checks the manifest.
370
+ */
371
+ export function unregisteredPresetIds(ids, lookup) {
372
+ return [...new Set(ids)].filter((id) => Object.hasOwn(OAUTH_PRESETS, id) && lookup.get(id) === undefined);
373
+ }
374
+ /**
375
+ * `lookup` with every preset it lacks, as the row it would be: what a
376
+ * local run (`everyapp dev`) and a check made before a Gateway is asked
377
+ * read, since a deploy writes the row of any preset an app names.
378
+ */
379
+ export function withOAuthPresets(lookup) {
380
+ const presets = oauthPresetIds().filter((id) => !lookup.get(id));
381
+ return {
382
+ get: (id) => lookup.get(id) ??
383
+ (presets.includes(id) ? (oauthPreset(id) ?? undefined) : undefined),
384
+ ids: () => [...lookup.ids(), ...presets],
385
+ };
1064
386
  }