@every-app/sdk 0.3.0-rc.1 → 0.3.0-rc.10

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +81 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +174 -0
  6. package/dist/cloudflare/index.d.ts +3 -2
  7. package/dist/cloudflare/index.d.ts.map +1 -1
  8. package/dist/cloudflare/index.js +4 -1
  9. package/dist/cloudflare/sentinel.d.ts +60 -2
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -1
  11. package/dist/cloudflare/sentinel.js +37 -2
  12. package/dist/cloudflare/worker.d.ts +357 -63
  13. package/dist/cloudflare/worker.d.ts.map +1 -1
  14. package/dist/cloudflare/worker.js +363 -104
  15. package/dist/dev/index.d.ts +1 -1
  16. package/dist/dev/index.d.ts.map +1 -1
  17. package/dist/dev/index.js +1 -1
  18. package/dist/dev/keypair.d.ts +2 -0
  19. package/dist/dev/keypair.d.ts.map +1 -1
  20. package/dist/dev/keypair.js +2 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +204 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +334 -0
  27. package/dist/internal/aiProviders.d.ts +7 -2
  28. package/dist/internal/aiProviders.d.ts.map +1 -1
  29. package/dist/internal/aiProviders.js +17 -4
  30. package/dist/internal/appSettings.d.ts +82 -4
  31. package/dist/internal/appSettings.d.ts.map +1 -1
  32. package/dist/internal/appSettings.js +204 -29
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +2268 -438
  37. package/dist/internal/capabilities.d.ts.map +1 -1
  38. package/dist/internal/capabilities.js +128 -539
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +1973 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +423 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +543 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +2 -2
  58. package/dist/internal/devProviders.d.ts.map +1 -1
  59. package/dist/internal/devProviders.js +19 -1
  60. package/dist/internal/events.d.ts +13 -2
  61. package/dist/internal/events.d.ts.map +1 -1
  62. package/dist/internal/events.js +47 -4
  63. package/dist/internal/gatewaySettings.d.ts +3 -2
  64. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  65. package/dist/internal/gatewaySettings.js +12 -6
  66. package/dist/internal/identity.d.ts +41 -4
  67. package/dist/internal/identity.d.ts.map +1 -1
  68. package/dist/internal/identity.js +48 -10
  69. package/dist/internal/index.d.ts +6 -1
  70. package/dist/internal/index.d.ts.map +1 -1
  71. package/dist/internal/index.js +6 -1
  72. package/dist/internal/members.d.ts +3 -2
  73. package/dist/internal/members.d.ts.map +1 -1
  74. package/dist/internal/oauthClient.d.ts +10 -12
  75. package/dist/internal/oauthClient.d.ts.map +1 -1
  76. package/dist/internal/oauthClient.js +20 -81
  77. package/dist/internal/oauthPresets.d.ts +33 -0
  78. package/dist/internal/oauthPresets.d.ts.map +1 -0
  79. package/dist/internal/oauthPresets.js +1064 -0
  80. package/dist/internal/oauthScopes.d.ts +56 -1
  81. package/dist/internal/oauthScopes.d.ts.map +1 -1
  82. package/dist/internal/oauthScopes.js +98 -4
  83. package/dist/internal/oauthSpec.d.ts +102 -0
  84. package/dist/internal/oauthSpec.d.ts.map +1 -0
  85. package/dist/internal/oauthSpec.js +789 -0
  86. package/dist/internal/protocol.d.ts +64 -1
  87. package/dist/internal/protocol.d.ts.map +1 -1
  88. package/dist/internal/protocol.js +51 -1
  89. package/dist/internal/providerRegistry.d.ts +77 -6
  90. package/dist/internal/providerRegistry.d.ts.map +1 -1
  91. package/dist/internal/providerRegistry.js +178 -8
  92. package/dist/internal/providerSpec.d.ts +18 -7
  93. package/dist/internal/providerSpec.d.ts.map +1 -1
  94. package/dist/internal/providerSpec.js +442 -34
  95. package/dist/internal/signInProviders.d.ts +104 -0
  96. package/dist/internal/signInProviders.d.ts.map +1 -0
  97. package/dist/internal/signInProviders.js +245 -0
  98. package/dist/manifest.d.ts +238 -0
  99. package/dist/manifest.d.ts.map +1 -0
  100. package/dist/manifest.js +30 -0
  101. package/dist/server/ai.d.ts +111 -0
  102. package/dist/server/ai.d.ts.map +1 -0
  103. package/dist/server/ai.js +145 -0
  104. package/dist/server/aiBinding.d.ts +68 -0
  105. package/dist/server/aiBinding.d.ts.map +1 -0
  106. package/dist/server/aiBinding.js +294 -0
  107. package/dist/server/attribution.d.ts +9 -5
  108. package/dist/server/attribution.d.ts.map +1 -1
  109. package/dist/server/attribution.js +12 -4
  110. package/dist/server/authorizeUrl.d.ts +1 -1
  111. package/dist/server/authorizeUrl.d.ts.map +1 -1
  112. package/dist/server/authorizeUrl.js +2 -6
  113. package/dist/server/connectUrl.d.ts +21 -0
  114. package/dist/server/connectUrl.d.ts.map +1 -0
  115. package/dist/server/connectUrl.js +37 -0
  116. package/dist/server/context.d.ts +68 -0
  117. package/dist/server/context.d.ts.map +1 -0
  118. package/dist/server/context.js +117 -0
  119. package/dist/server/emailBinding.d.ts +72 -0
  120. package/dist/server/emailBinding.d.ts.map +1 -0
  121. package/dist/server/emailBinding.js +124 -0
  122. package/dist/server/emit.d.ts +1 -1
  123. package/dist/server/emit.d.ts.map +1 -1
  124. package/dist/server/emit.js +7 -7
  125. package/dist/server/env.d.ts +44 -0
  126. package/dist/server/env.d.ts.map +1 -0
  127. package/dist/server/env.js +1 -0
  128. package/dist/server/everyApp.d.ts +27 -7
  129. package/dist/server/everyApp.d.ts.map +1 -1
  130. package/dist/server/everyApp.js +106 -5
  131. package/dist/server/fromMcpTools.d.ts +89 -0
  132. package/dist/server/fromMcpTools.d.ts.map +1 -0
  133. package/dist/server/fromMcpTools.js +192 -0
  134. package/dist/server/gatewayFetch.d.ts +42 -18
  135. package/dist/server/gatewayFetch.d.ts.map +1 -1
  136. package/dist/server/gatewayFetch.js +138 -19
  137. package/dist/server/gatewayUrl.d.ts +13 -0
  138. package/dist/server/gatewayUrl.d.ts.map +1 -0
  139. package/dist/server/gatewayUrl.js +34 -0
  140. package/dist/server/index.d.ts +15 -4
  141. package/dist/server/index.d.ts.map +1 -1
  142. package/dist/server/index.js +12 -2
  143. package/dist/server/integrationFetch.d.ts +70 -39
  144. package/dist/server/integrationFetch.d.ts.map +1 -1
  145. package/dist/server/integrationFetch.js +244 -82
  146. package/dist/server/listMembers.d.ts +1 -1
  147. package/dist/server/listMembers.d.ts.map +1 -1
  148. package/dist/server/listMembers.js +8 -11
  149. package/dist/server/refusal.d.ts +55 -0
  150. package/dist/server/refusal.d.ts.map +1 -0
  151. package/dist/server/refusal.js +193 -0
  152. package/dist/server/sendEmail.d.ts +1 -1
  153. package/dist/server/sendEmail.d.ts.map +1 -1
  154. package/dist/server/sendEmail.js +7 -7
  155. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  156. package/dist/server/toolsEndpoint.js +11 -5
  157. package/dist/tanstack-start/index.d.ts +9 -0
  158. package/dist/tanstack-start/index.d.ts.map +1 -0
  159. package/dist/tanstack-start/index.js +29 -0
  160. package/dist/testing/index.d.ts +93 -0
  161. package/dist/testing/index.d.ts.map +1 -0
  162. package/dist/testing/index.js +156 -0
  163. package/package.json +35 -14
@@ -0,0 +1,1064 @@
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
+ */
20
+ import { parseProviderSpec, } from "./providerSpec.js";
21
+ export const OAUTH_PRESETS = {
22
+ "github-oauth": {
23
+ id: "github-oauth",
24
+ name: "GitHub",
25
+ description: "Repositories, issues, pull requests and the person's profile.",
26
+ docs: "https://github.com/settings/developers",
27
+ api: "https://docs.github.com/rest",
28
+ hosts: ["api.github.com"],
29
+ auth: {
30
+ kind: "oauth2-user",
31
+ authorizeUrl: "https://github.com/login/oauth/authorize",
32
+ tokenUrl: "https://github.com/login/oauth/access_token",
33
+ revokeUrl: "https://api.github.com/applications/{client_id}/grant",
34
+ revokeMethod: "DELETE",
35
+ clientId: "GITHUB_OAUTH_CLIENT_ID",
36
+ clientSecret: "GITHUB_OAUTH_CLIENT_SECRET",
37
+ inject: "form",
38
+ pkce: true,
39
+ scopeDialect: "comma",
40
+ tokenHeaders: { Accept: "application/json" },
41
+ identity: {
42
+ url: "https://api.github.com/user",
43
+ label: "login",
44
+ id: "id",
45
+ },
46
+ },
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.",
71
+ },
72
+ "notion-oauth": {
73
+ id: "notion-oauth",
74
+ name: "Notion",
75
+ description: "Pages and databases the person shares with the integration.",
76
+ docs: "https://www.notion.so/my-integrations",
77
+ api: "https://developers.notion.com/reference",
78
+ hosts: ["api.notion.com"],
79
+ auth: {
80
+ kind: "oauth2-user",
81
+ authorizeUrl: "https://api.notion.com/v1/oauth/authorize",
82
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
83
+ clientId: "NOTION_OAUTH_CLIENT_ID",
84
+ clientSecret: "NOTION_OAUTH_CLIENT_SECRET",
85
+ inject: "basic",
86
+ pkce: false,
87
+ scopeDialect: "space",
88
+ authorizeParams: { owner: "user" },
89
+ },
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.",
121
+ },
122
+ "linear-oauth": {
123
+ id: "linear-oauth",
124
+ name: "Linear",
125
+ description: "Issues, projects and teams, over Linear's GraphQL API.",
126
+ docs: "https://linear.app/settings/api/applications",
127
+ api: "https://developers.linear.app/docs/oauth/authentication",
128
+ hosts: ["api.linear.app"],
129
+ auth: {
130
+ kind: "oauth2-user",
131
+ authorizeUrl: "https://linear.app/oauth/authorize",
132
+ tokenUrl: "https://api.linear.app/oauth/token",
133
+ revokeUrl: "https://api.linear.app/oauth/revoke",
134
+ clientId: "LINEAR_OAUTH_CLIENT_ID",
135
+ clientSecret: "LINEAR_OAUTH_CLIENT_SECRET",
136
+ inject: "form",
137
+ 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
+ },
150
+ },
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.",
155
+ },
156
+ "gitlab-oauth": {
157
+ id: "gitlab-oauth",
158
+ name: "GitLab",
159
+ description: "Projects, merge requests and issues on gitlab.com.",
160
+ docs: "https://gitlab.com/-/user_settings/applications",
161
+ api: "https://docs.gitlab.com/api/rest/",
162
+ hosts: ["gitlab.com"],
163
+ auth: {
164
+ kind: "oauth2-user",
165
+ authorizeUrl: "https://gitlab.com/oauth/authorize",
166
+ tokenUrl: "https://gitlab.com/oauth/token",
167
+ revokeUrl: "https://gitlab.com/oauth/revoke",
168
+ clientId: "GITLAB_OAUTH_CLIENT_ID",
169
+ clientSecret: "GITLAB_OAUTH_CLIENT_SECRET",
170
+ inject: "form",
171
+ pkce: true,
172
+ scopeDialect: "space",
173
+ identity: {
174
+ url: "https://gitlab.com/api/v4/user",
175
+ label: "username",
176
+ id: "id",
177
+ },
178
+ },
179
+ scopeSets: {
180
+ identity: {
181
+ label: "GitLab account (who you are)",
182
+ access: "read",
183
+ scopes: ["read_user"],
184
+ },
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
+ },
196
+ access: ["read", "write"],
197
+ hint: "A self-managed GitLab needs a custom provider with its own host in place of gitlab.com.",
198
+ },
199
+ atlassian: {
200
+ id: "atlassian",
201
+ name: "Atlassian (Jira and Confluence)",
202
+ description: "Jira issues and Confluence pages on the sites the person grants.",
203
+ docs: "https://developer.atlassian.com/console/myapps/",
204
+ api: "https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/",
205
+ hosts: ["api.atlassian.com"],
206
+ auth: {
207
+ kind: "oauth2-user",
208
+ authorizeUrl: "https://auth.atlassian.com/authorize",
209
+ tokenUrl: "https://auth.atlassian.com/oauth/token",
210
+ clientId: "ATLASSIAN_CLIENT_ID",
211
+ clientSecret: "ATLASSIAN_CLIENT_SECRET",
212
+ inject: "form",
213
+ pkce: false,
214
+ scopeDialect: "space",
215
+ authorizeParams: { audience: "api.atlassian.com", prompt: "consent" },
216
+ identity: {
217
+ url: "https://api.atlassian.com/me",
218
+ label: "email",
219
+ id: "account_id",
220
+ },
221
+ },
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
+ hint: "Calls go to api.atlassian.com/ex/jira/<cloud id>/… ; the cloud id comes from /oauth/token/accessible-resources.",
250
+ },
251
+ "hubspot-oauth": {
252
+ id: "hubspot-oauth",
253
+ name: "HubSpot",
254
+ description: "CRM contacts, companies and deals.",
255
+ docs: "https://developers.hubspot.com/",
256
+ api: "https://developers.hubspot.com/docs/reference/api",
257
+ hosts: ["api.hubapi.com"],
258
+ auth: {
259
+ kind: "oauth2-user",
260
+ authorizeUrl: "https://app.hubspot.com/oauth/authorize",
261
+ tokenUrl: "https://api.hubapi.com/oauth/v1/token",
262
+ clientId: "HUBSPOT_OAUTH_CLIENT_ID",
263
+ clientSecret: "HUBSPOT_OAUTH_CLIENT_SECRET",
264
+ inject: "form",
265
+ 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
+ },
289
+ },
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.",
292
+ },
293
+ zoom: {
294
+ id: "zoom",
295
+ name: "Zoom",
296
+ description: "The person's meetings and recordings.",
297
+ docs: "https://marketplace.zoom.us/develop/create",
298
+ api: "https://developers.zoom.us/docs/api/",
299
+ hosts: ["api.zoom.us"],
300
+ auth: {
301
+ kind: "oauth2-user",
302
+ authorizeUrl: "https://zoom.us/oauth/authorize",
303
+ tokenUrl: "https://zoom.us/oauth/token",
304
+ revokeUrl: "https://zoom.us/oauth/revoke",
305
+ clientId: "ZOOM_CLIENT_ID",
306
+ clientSecret: "ZOOM_CLIENT_SECRET",
307
+ inject: "basic",
308
+ pkce: true,
309
+ scopeDialect: "space",
310
+ identity: {
311
+ url: "https://api.zoom.us/v2/users/me",
312
+ label: "email",
313
+ id: "id",
314
+ },
315
+ },
316
+ scopeSets: {
317
+ identity: {
318
+ label: "Zoom account (who you are)",
319
+ access: "read",
320
+ scopes: ["user:read:user"],
321
+ },
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
+ },
342
+ access: ["read", "write"],
343
+ hint: "A General app with user-managed OAuth; Zoom's granular scope names are the ones listed here.",
344
+ },
345
+ dropbox: {
346
+ id: "dropbox",
347
+ name: "Dropbox",
348
+ description: "Files and folders in the person's Dropbox.",
349
+ docs: "https://www.dropbox.com/developers/apps",
350
+ api: "https://www.dropbox.com/developers/documentation/http/documentation",
351
+ hosts: ["api.dropboxapi.com", "content.dropboxapi.com"],
352
+ auth: {
353
+ kind: "oauth2-user",
354
+ authorizeUrl: "https://www.dropbox.com/oauth2/authorize",
355
+ tokenUrl: "https://api.dropboxapi.com/oauth2/token",
356
+ revokeUrl: "https://api.dropboxapi.com/2/auth/token/revoke",
357
+ clientId: "DROPBOX_OAUTH_CLIENT_ID",
358
+ clientSecret: "DROPBOX_OAUTH_CLIENT_SECRET",
359
+ inject: "form",
360
+ pkce: true,
361
+ scopeDialect: "space",
362
+ authorizeParams: { token_access_type: "offline" },
363
+ },
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.',
424
+ },
425
+ spotify: {
426
+ id: "spotify",
427
+ name: "Spotify",
428
+ description: "The person's library, playlists and listening.",
429
+ docs: "https://developer.spotify.com/dashboard",
430
+ api: "https://developer.spotify.com/documentation/web-api",
431
+ hosts: ["api.spotify.com"],
432
+ auth: {
433
+ kind: "oauth2-user",
434
+ authorizeUrl: "https://accounts.spotify.com/authorize",
435
+ tokenUrl: "https://accounts.spotify.com/api/token",
436
+ clientId: "SPOTIFY_CLIENT_ID",
437
+ clientSecret: "SPOTIFY_CLIENT_SECRET",
438
+ inject: "basic",
439
+ pkce: true,
440
+ scopeDialect: "space",
441
+ identity: {
442
+ url: "https://api.spotify.com/v1/me",
443
+ label: "display_name",
444
+ id: "id",
445
+ },
446
+ },
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
+ },
1045
+ };
1046
+ /** The preset ids, in the order the picker shows them. */
1047
+ export function oauthPresetIds() {
1048
+ return Object.keys(OAUTH_PRESETS);
1049
+ }
1050
+ /** A preset's parsed capability, for the picker's labels; throws for an unknown id (the test parses every preset). */
1051
+ export function oauthPreset(id) {
1052
+ const spec = OAUTH_PRESETS[id];
1053
+ if (!spec)
1054
+ return null;
1055
+ const parsed = parseProviderSpec(spec);
1056
+ if (!parsed.ok) {
1057
+ throw new Error(`preset ${id} doesn't parse: ${JSON.stringify(parsed.problems)}`);
1058
+ }
1059
+ return parsed.capability;
1060
+ }
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;
1064
+ }