@withone/connect 0.17.2 → 0.19.0

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.
package/README.md CHANGED
@@ -84,6 +84,41 @@ ONE_REDIRECT_URI=https://yourapp.com/api/one/callback # exactly the registered
84
84
  ONE_PERMISSION_SET=… # optional: the Permission set ID
85
85
  ```
86
86
 
87
+ ### Or from code
88
+
89
+ Describe the app in `one.connect.ts` and create it with the [One CLI](https://www.npmjs.com/package/@withone/cli) (2.10.0 or later). Running it again updates the same app.
90
+
91
+ ```ts
92
+ // one.connect.ts
93
+ import { defineConnectApp } from "@withone/connect/config";
94
+
95
+ export default defineConnectApp({
96
+ name: "Mastra",
97
+ logoUrl: "https://mastra.ai/logo.png",
98
+ redirectUris: ["http://localhost:3000/api/one/callback"],
99
+ ask: {
100
+ gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
101
+ "google-calendar": { access: "read-only" },
102
+ linear: { access: "read-write" },
103
+ },
104
+ });
105
+ ```
106
+
107
+ ```bash
108
+ one connect types # optional: tool slugs and action names autocomplete
109
+ one connect create # opens your browser to approve it, then writes .env
110
+ ```
111
+
112
+ The CLI opens a page in your signed-in browser showing the app as your users will see it. You approve it there; a new app's client secret and connect key are made on that page and shown once, so they never pass through the terminal. The CLI then writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to `.env`. `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`, named as `one connect types` wrote them or by id. On Node older than 22.18, name the file `one.connect.mjs` and type it with `/** @type {import("@withone/connect/config").ConnectAppConfig} */`.
113
+
114
+ | Prop | |
115
+ |---|---|
116
+ | `name`, `redirectUris` | Required. Every callback URL, character for character. |
117
+ | `reason` | One sentence on the card telling users why, at most 200 characters. |
118
+ | `description`, `logoUrl`, `homepageUrl` | Optional. |
119
+ | `branding` | `panelColor`, `buttonColor` (`#RRGGBB`), `cobrandEmail`. |
120
+ | `ask` | Per tool slug: `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`. Omit it to let users choose from everything they have connected. |
121
+
87
122
  ## 2 · The server client
88
123
 
89
124
  Your server holds one **connect key** for the app and saves one id per user who connects. Nothing expires and nothing is refreshed: One checks the user's consent on every call.
@@ -302,6 +337,21 @@ await oneConnect.runAction(userId, {
302
337
 
303
338
  `listActions(userId, platform)` lists everything a platform can do, and `runAction` takes `method` and `path` from it if you'd rather pass them yourself. `oneConnect.fetch(userId, path, init)` sends any other request to One's `/v1` API with the headers handled.
304
339
 
340
+ ### Who connected
341
+
342
+ You can also read who the user is on One: their name and email. Use it if you want to, for example to show which One account is connected.
343
+
344
+ ```ts
345
+ // Key mode
346
+ const res = await oneConnect.fetch(userId, "/users/whoami");
347
+ const { user } = await res.json(); // { id, name, email }
348
+
349
+ // Token mode
350
+ const me = await (await oneConnect.fetch(userId, "/users/me")).json(); // { firstName, lastName, email, … }
351
+ ```
352
+
353
+ Users who connected with a version before 0.18.0 did not grant this, so the call returns `403` until they connect again.
354
+
305
355
  You never set an auth header: the client adds the connect key and the user's id to every call it makes.
306
356
 
307
357
  ## 6 · Errors
@@ -429,7 +479,7 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
429
479
  | `mode` | `"key"` or `"token"`, to state the mode explicitly. |
430
480
  | `permissionSet` | The Permission set ID to ask for. |
431
481
  | `returnTo` | Where the callback sends the browser afterwards. `/` by default. |
432
- | `scopes` | OAuth scopes. All three spaces (personal, organization, project) by default, so the user may grant from any. |
482
+ | `scopes` | OAuth scopes. By default `user:profile:read` (who connected) and the connections scopes of all three spaces (personal, organization, project), so the user may grant from any. |
433
483
  | `oneApiUrl` | One's API origin. Production by default. |
434
484
 
435
485
  | Method | Mode | |
@@ -469,6 +519,14 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
469
519
 
470
520
  `useOneConnect` and `createConnectFlow` take `authorizeUrl`, `connectTheme`, `connector`, `returnTo`, `onSuccess`, `onError` and `onCancel`.
471
521
 
522
+ ### Config · `@withone/connect/config`
523
+
524
+ | Export | |
525
+ |---|---|
526
+ | `defineConnectApp(config)` | Returns `config` unchanged; types `one.connect.ts`. |
527
+ | `ConnectAppConfig`, `ConnectToolAsk`, `ConnectAccess`, `ConnectAppBranding` | The config's types. |
528
+ | `ConnectRegistry` | Filled in by `one connect types` so slugs and action names autocomplete. |
529
+
472
530
  ## Support
473
531
 
474
532
  - Bugs and questions: [GitHub issues](https://github.com/withoneai/connect/issues).
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * A Connect app described as code, for `one connect create`.
5
+ *
6
+ * // one.connect.ts
7
+ * import { defineConnectApp } from "@withone/connect/config";
8
+ *
9
+ * export default defineConnectApp({
10
+ * name: "Mastra",
11
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
12
+ * ask: {
13
+ * gmail: { access: "custom", actions: ["sendEmail"] },
14
+ * "google-calendar": { access: "read-only" },
15
+ * linear: { access: "read-write" },
16
+ * },
17
+ * });
18
+ *
19
+ * Types and one identity helper; nothing here calls One. `one connect
20
+ * create` reads the file and opens a page in your signed-in browser, where
21
+ * you approve the app (or, on a re-run, its new version). A new app's
22
+ * client secret and connect key are made and shown there once; neither is
23
+ * ever part of this file or passes through the CLI.
24
+ */
25
+
26
+ /**
27
+ * Filled in by `one connect types`, which writes a declaration file naming
28
+ * every tool and, for the tools your `ask` names, every action:
29
+ *
30
+ * declare module "@withone/connect/config" {
31
+ * interface ConnectRegistry {
32
+ * tools: "gmail" | "google-calendar" | "linear" | …;
33
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
34
+ * }
35
+ * }
36
+ *
37
+ * Until it runs, any tool slug and any action name type-check.
38
+ */
39
+
40
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
41
+
42
+ /** An action named for the config: its title in camel case ("sendEmail"),
43
+ * from the names `one connect types` wrote for that tool. */
44
+
45
+ /** An action as `actions` takes it: by name, or by its id from One's
46
+ * catalog (`one actions search` prints it). */
47
+
48
+ /**
49
+ * How much of a tool the app asks for, in the dashboard's words.
50
+ * - `read-only`: reads only.
51
+ * - `read-write`: reads, creates and updates; never deletes.
52
+ * - `full-access`: everything the tool offers.
53
+ */
54
+
55
+ /** One tool's ask: a level, or a hand-picked list of actions. */
56
+
57
+ /** A colour as `#RRGGBB`. */
58
+
59
+ /** How the connect card and its sign-in email look. Omit it to keep
60
+ * One's own look. */
61
+
62
+ /**
63
+ * Returns `config` unchanged. It exists for the editor: the argument is
64
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
65
+ * tool slugs and action names autocomplete.
66
+ */
67
+ function defineConnectApp(config) {
68
+ return config;
69
+ }
70
+
71
+ exports.defineConnectApp = defineConnectApp;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+ /**
24
+ * Filled in by `one connect types`, which writes a declaration file naming
25
+ * every tool and, for the tools your `ask` names, every action:
26
+ *
27
+ * declare module "@withone/connect/config" {
28
+ * interface ConnectRegistry {
29
+ * tools: "gmail" | "google-calendar" | "linear" | …;
30
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
31
+ * }
32
+ * }
33
+ *
34
+ * Until it runs, any tool slug and any action name type-check.
35
+ */
36
+ export interface ConnectRegistry {
37
+ }
38
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
39
+ export type ConnectToolSlug = ConnectRegistry extends {
40
+ tools: infer T extends string;
41
+ } ? T : string;
42
+ /** An action named for the config: its title in camel case ("sendEmail"),
43
+ * from the names `one connect types` wrote for that tool. */
44
+ export type ConnectActionName<T extends string> = ConnectRegistry extends {
45
+ actions: infer A;
46
+ } ? T extends keyof A ? A[T] & string : string : string;
47
+ /** An action as `actions` takes it: by name, or by its id from One's
48
+ * catalog (`one actions search` prints it). */
49
+ export type ConnectActionRef<T extends string> = ConnectActionName<T> | `conn_mod_def::${string}`;
50
+ /**
51
+ * How much of a tool the app asks for, in the dashboard's words.
52
+ * - `read-only`: reads only.
53
+ * - `read-write`: reads, creates and updates; never deletes.
54
+ * - `full-access`: everything the tool offers.
55
+ */
56
+ export type ConnectAccess = "read-only" | "read-write" | "full-access";
57
+ /** One tool's ask: a level, or a hand-picked list of actions. */
58
+ export type ConnectToolAsk<T extends string = string> = {
59
+ access: ConnectAccess;
60
+ } | {
61
+ access: "custom";
62
+ actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
63
+ };
64
+ /** A colour as `#RRGGBB`. */
65
+ export type ConnectHexColor = `#${string}`;
66
+ /** How the connect card and its sign-in email look. Omit it to keep
67
+ * One's own look. */
68
+ export interface ConnectAppBranding {
69
+ /** The card's side panel. */
70
+ panelColor: ConnectHexColor;
71
+ /** Authorize, Continue and the other primary buttons. */
72
+ buttonColor: ConnectHexColor;
73
+ /** Whether the sign-in email carries the app's name, logo and colours.
74
+ * `false` when omitted. */
75
+ cobrandEmail?: boolean;
76
+ }
77
+ export interface ConnectAppConfig {
78
+ /** Shown on the connect card and in the user's list of apps. */
79
+ name: string;
80
+ /** Every callback URL the app's routes run on, character for character:
81
+ * local first is fine, then production. */
82
+ redirectUris: readonly [string, ...string[]];
83
+ description?: string;
84
+ /** A public https URL to a square image. */
85
+ logoUrl?: string;
86
+ homepageUrl?: string;
87
+ /** One sentence on the card telling users why the app asks, at most
88
+ * 200 characters. */
89
+ reason?: string;
90
+ branding?: ConnectAppBranding;
91
+ /** The tools the app asks for, keyed by slug. Omit it to let users
92
+ * choose from everything they have connected. */
93
+ ask?: {
94
+ [T in ConnectToolSlug]?: ConnectToolAsk<T>;
95
+ };
96
+ }
97
+ /**
98
+ * Returns `config` unchanged. It exists for the editor: the argument is
99
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
100
+ * tool slugs and action names autocomplete.
101
+ */
102
+ export declare function defineConnectApp<const C extends ConnectAppConfig>(config: C): C;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+
24
+ /**
25
+ * Filled in by `one connect types`, which writes a declaration file naming
26
+ * every tool and, for the tools your `ask` names, every action:
27
+ *
28
+ * declare module "@withone/connect/config" {
29
+ * interface ConnectRegistry {
30
+ * tools: "gmail" | "google-calendar" | "linear" | …;
31
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
32
+ * }
33
+ * }
34
+ *
35
+ * Until it runs, any tool slug and any action name type-check.
36
+ */
37
+
38
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
39
+
40
+ /** An action named for the config: its title in camel case ("sendEmail"),
41
+ * from the names `one connect types` wrote for that tool. */
42
+
43
+ /** An action as `actions` takes it: by name, or by its id from One's
44
+ * catalog (`one actions search` prints it). */
45
+
46
+ /**
47
+ * How much of a tool the app asks for, in the dashboard's words.
48
+ * - `read-only`: reads only.
49
+ * - `read-write`: reads, creates and updates; never deletes.
50
+ * - `full-access`: everything the tool offers.
51
+ */
52
+
53
+ /** One tool's ask: a level, or a hand-picked list of actions. */
54
+
55
+ /** A colour as `#RRGGBB`. */
56
+
57
+ /** How the connect card and its sign-in email look. Omit it to keep
58
+ * One's own look. */
59
+
60
+ /**
61
+ * Returns `config` unchanged. It exists for the editor: the argument is
62
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
63
+ * tool slugs and action names autocomplete.
64
+ */
65
+ function defineConnectApp(config) {
66
+ return config;
67
+ }
68
+
69
+ export { defineConnectApp };
@@ -20,7 +20,10 @@ const TX_COOKIE_PREFIX = "one_tx_";
20
20
  * legitimate callback can arrive up to twenty minutes after the start.
21
21
  * Thirty minutes leaves a margin. */
22
22
  const TX_COOKIE_MAX_AGE_SECONDS = 1800;
23
- const DEFAULT_SCOPES = ["user:connections:read", "user:connections:write", "org:connections:read", "org:connections:write", "project:connections:read", "project:connections:write"];
23
+ const DEFAULT_SCOPES = [
24
+ // Who connected: lets the app read the user's name and email from
25
+ // /v1/users/whoami (key mode) or /v1/users/me (token mode).
26
+ "user:profile:read", "user:connections:read", "user:connections:write", "org:connections:read", "org:connections:write", "project:connections:read", "project:connections:write"];
24
27
  function createState() {
25
28
  return node_crypto.randomBytes(16).toString("hex");
26
29
  }
@@ -18,7 +18,10 @@ const TX_COOKIE_PREFIX = "one_tx_";
18
18
  * legitimate callback can arrive up to twenty minutes after the start.
19
19
  * Thirty minutes leaves a margin. */
20
20
  const TX_COOKIE_MAX_AGE_SECONDS = 1800;
21
- const DEFAULT_SCOPES = ["user:connections:read", "user:connections:write", "org:connections:read", "org:connections:write", "project:connections:read", "project:connections:write"];
21
+ const DEFAULT_SCOPES = [
22
+ // Who connected: lets the app read the user's name and email from
23
+ // /v1/users/whoami (key mode) or /v1/users/me (token mode).
24
+ "user:profile:read", "user:connections:read", "user:connections:write", "org:connections:read", "org:connections:write", "project:connections:read", "project:connections:write"];
22
25
  function createState() {
23
26
  return randomBytes(16).toString("hex");
24
27
  }
@@ -102,7 +102,8 @@ export interface OneConnectBaseConfig {
102
102
  /** Where the callback sends the browser afterwards. "/" when omitted;
103
103
  * the outcome is appended as `?one_connect=…`. */
104
104
  returnTo?: string;
105
- /** OAuth scopes. All three tenancy tiers when omitted, so the user may
105
+ /** OAuth scopes. When omitted: `user:profile:read` (who connected) and
106
+ * the connections scopes of all three tenancy tiers, so the user may
106
107
  * grant from any space. */
107
108
  scopes?: string[];
108
109
  }
package/dist/types.d.ts CHANGED
@@ -77,9 +77,9 @@ export interface ConnectButtonLogo {
77
77
  name: string;
78
78
  imageUrl: string;
79
79
  }
80
- /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.18.0. */
80
+ /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
81
81
  export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
82
- /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.18.0. */
82
+ /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
83
83
  export type ConnectButtonPlatform = ConnectButtonLogo;
84
84
  export type ConnectButtonVariant = "default" | "accent" | "block";
85
85
  export type ConnectButtonSize = "sm" | "md" | "lg";
@@ -94,7 +94,7 @@ export interface ConnectButtonProps {
94
94
  * into a "+N" chip. Decoration only; the permission set decides what
95
95
  * One asks for. */
96
96
  logos?: ConnectButtonLogoInput[];
97
- /** @deprecated Renamed to `logos`; removed in 0.18.0. */
97
+ /** @deprecated Renamed to `logos`; removed in 0.20.0. */
98
98
  platforms?: ConnectButtonLogoInput[];
99
99
  /** Whether this user has a live grant, from your server. When set, it
100
100
  * decides the Connected state. When omitted, the button shows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.17.2",
3
+ "version": "0.19.0",
4
4
  "description": "One Connect for your app: the button your users press, the two backend routes as one import, and a server client that calls One with the grant. Users keep their connections in One; your app holds only what they granted.",
5
5
  "files": [
6
6
  "dist",
@@ -105,6 +105,11 @@
105
105
  "import": "./dist/node.esm.js",
106
106
  "require": "./dist/node.cjs.js"
107
107
  },
108
+ "./config": {
109
+ "types": "./dist/config.d.ts",
110
+ "import": "./dist/config.esm.js",
111
+ "require": "./dist/config.cjs.js"
112
+ },
108
113
  "./package.json": "./package.json"
109
114
  },
110
115
  "peerDependencies": {
@@ -39,7 +39,66 @@ key mode; section 10 lists what changes for token mode.
39
39
 
40
40
  ## 2 - Ask the human for these
41
41
 
42
- They create the app in the One dashboard: Developers -> Connect -> New app.
42
+ If the human has the One CLI (2.10.0 or later), create the app from code
43
+ instead of asking for its values.
44
+
45
+ **Before you start.** The CLI must be logged in: `one login` (or
46
+ `one init`) gives the CLI its own One API key, kept in `~/.one`. Never put
47
+ that key in the app's `.env`; the app never needs it. Install the SDK in the
48
+ project first (`npm i @withone/connect`, 0.19.0 or later): the config file
49
+ imports it.
50
+
51
+ **1. Write `one.connect.ts`** at the project root:
52
+
53
+ ```ts
54
+ import { defineConnectApp } from "@withone/connect/config";
55
+
56
+ export default defineConnectApp({
57
+ name: "Acme Assistant", // shown on the connect card
58
+ redirectUris: ["http://localhost:3000/api/one/callback"], // every callback URL, exact
59
+ reason: "Acme Assistant reads your mail and plans your week", // optional, at most 200 characters
60
+ description: "Your inbox and calendar, planned", // optional
61
+ logoUrl: "https://acme.app/logo.png", // optional, square, public https
62
+ homepageUrl: "https://acme.app", // optional
63
+ branding: { panelColor: "#0A0C0B", buttonColor: "#0A0C0B", cobrandEmail: false }, // optional
64
+ ask: { // optional: omit to let users pick from everything they have
65
+ gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
66
+ "google-calendar": { access: "full-access" },
67
+ notion: { access: "read-write" },
68
+ },
69
+ });
70
+ ```
71
+
72
+ Each tool in `ask` is keyed by its slug and takes `access`: `read-only`,
73
+ `read-write` (no deletes), `full-access`, or `custom` with `actions`.
74
+
75
+ **2. Find slugs and action names** with the CLI, which reads One's catalog
76
+ with its own key:
77
+
78
+ ```bash
79
+ one connect types # writes one-connect-env.d.ts: every slug, and action names for the tools in ask
80
+ one --agent platforms # every tool slug
81
+ one --agent actions find gmail "send an email" --no-knowledge # an action's id and title
82
+ ```
83
+
84
+ Use the names `one connect types` wrote, or an action's id
85
+ (`conn_mod_def::…`). A wrong name stops `connect create` before anything
86
+ is made.
87
+
88
+ **3. Run `one --agent connect create`.** It opens a page in the human's
89
+ browser and waits up to 10 minutes, so run it in the background or with a
90
+ long timeout; the link goes to stderr at once, so pass it on if no browser
91
+ opened. The human sees the app as their users will, and approves it. For a
92
+ new app that page makes `ONE_CLIENT_SECRET` and `ONE_CONNECT_KEY` and shows
93
+ them once: tell the human to copy both into `.env`. Once approved, the CLI
94
+ writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to
95
+ `.env` and prints `"outcome": "created"`.
96
+
97
+ **To change the app later,** edit `one.connect.ts` and run
98
+ `one --agent connect create` again. The human approves the new version in
99
+ the browser (`"outcome": "updated"`); the secrets stay as they are.
100
+
101
+ Otherwise they create the app in the One dashboard: Developers -> Connect -> New app.
43
102
 
44
103
  | Value | Notes |
45
104
  |---|---|
@@ -225,6 +284,15 @@ itself (tag `custom`), and encodes the body as asked. `listActions(userId,
225
284
  platform)` lists a whole catalog when search is not enough;
226
285
  `oneConnect.fetch(userId, path, init)` reaches any other `/v1` endpoint.
227
286
 
287
+ **Who connected.** The app can also read who the user is on One, their
288
+ name and email, if it wants to (for example to show which One account is
289
+ connected). Key mode: `oneConnect.fetch(userId, "/users/whoami")` returns
290
+ `{ user: { id, name, email }, organization, project }`. Token mode:
291
+ `oneConnect.fetch(userId, "/users/me")` returns `{ firstName, lastName,
292
+ email, … }`. A user who connected before 0.18.0 gets `403` until they
293
+ connect again. Mention this to the human as something available; whether
294
+ and where to show it is their choice.
295
+
228
296
  Do not set any auth header. The package adds the connect key and the
229
297
  user's id to every call it makes.
230
298
 
package/src/config.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+
24
+ /**
25
+ * Filled in by `one connect types`, which writes a declaration file naming
26
+ * every tool and, for the tools your `ask` names, every action:
27
+ *
28
+ * declare module "@withone/connect/config" {
29
+ * interface ConnectRegistry {
30
+ * tools: "gmail" | "google-calendar" | "linear" | …;
31
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
32
+ * }
33
+ * }
34
+ *
35
+ * Until it runs, any tool slug and any action name type-check.
36
+ */
37
+ export interface ConnectRegistry {}
38
+
39
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
40
+ export type ConnectToolSlug = ConnectRegistry extends {
41
+ tools: infer T extends string;
42
+ }
43
+ ? T
44
+ : string;
45
+
46
+ /** An action named for the config: its title in camel case ("sendEmail"),
47
+ * from the names `one connect types` wrote for that tool. */
48
+ export type ConnectActionName<T extends string> = ConnectRegistry extends {
49
+ actions: infer A;
50
+ }
51
+ ? T extends keyof A
52
+ ? A[T] & string
53
+ : string
54
+ : string;
55
+
56
+ /** An action as `actions` takes it: by name, or by its id from One's
57
+ * catalog (`one actions search` prints it). */
58
+ export type ConnectActionRef<T extends string> =
59
+ ConnectActionName<T> | `conn_mod_def::${string}`;
60
+
61
+ /**
62
+ * How much of a tool the app asks for, in the dashboard's words.
63
+ * - `read-only`: reads only.
64
+ * - `read-write`: reads, creates and updates; never deletes.
65
+ * - `full-access`: everything the tool offers.
66
+ */
67
+ export type ConnectAccess = "read-only" | "read-write" | "full-access";
68
+
69
+ /** One tool's ask: a level, or a hand-picked list of actions. */
70
+ export type ConnectToolAsk<T extends string = string> =
71
+ | { access: ConnectAccess }
72
+ | {
73
+ access: "custom";
74
+ actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
75
+ };
76
+
77
+ /** A colour as `#RRGGBB`. */
78
+ export type ConnectHexColor = `#${string}`;
79
+
80
+ /** How the connect card and its sign-in email look. Omit it to keep
81
+ * One's own look. */
82
+ export interface ConnectAppBranding {
83
+ /** The card's side panel. */
84
+ panelColor: ConnectHexColor;
85
+ /** Authorize, Continue and the other primary buttons. */
86
+ buttonColor: ConnectHexColor;
87
+ /** Whether the sign-in email carries the app's name, logo and colours.
88
+ * `false` when omitted. */
89
+ cobrandEmail?: boolean;
90
+ }
91
+
92
+ export interface ConnectAppConfig {
93
+ /** Shown on the connect card and in the user's list of apps. */
94
+ name: string;
95
+ /** Every callback URL the app's routes run on, character for character:
96
+ * local first is fine, then production. */
97
+ redirectUris: readonly [string, ...string[]];
98
+ description?: string;
99
+ /** A public https URL to a square image. */
100
+ logoUrl?: string;
101
+ homepageUrl?: string;
102
+ /** One sentence on the card telling users why the app asks, at most
103
+ * 200 characters. */
104
+ reason?: string;
105
+ branding?: ConnectAppBranding;
106
+ /** The tools the app asks for, keyed by slug. Omit it to let users
107
+ * choose from everything they have connected. */
108
+ ask?: { [T in ConnectToolSlug]?: ConnectToolAsk<T> };
109
+ }
110
+
111
+ /**
112
+ * Returns `config` unchanged. It exists for the editor: the argument is
113
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
114
+ * tool slugs and action names autocomplete.
115
+ */
116
+ export function defineConnectApp<const C extends ConnectAppConfig>(
117
+ config: C,
118
+ ): C {
119
+ return config;
120
+ }
@@ -12,6 +12,9 @@ export const TX_COOKIE_PREFIX = "one_tx_";
12
12
  export const TX_COOKIE_MAX_AGE_SECONDS = 1800;
13
13
 
14
14
  export const DEFAULT_SCOPES = [
15
+ // Who connected: lets the app read the user's name and email from
16
+ // /v1/users/whoami (key mode) or /v1/users/me (token mode).
17
+ "user:profile:read",
15
18
  "user:connections:read",
16
19
  "user:connections:write",
17
20
  "org:connections:read",
@@ -109,7 +109,8 @@ export interface OneConnectBaseConfig {
109
109
  /** Where the callback sends the browser afterwards. "/" when omitted;
110
110
  * the outcome is appended as `?one_connect=…`. */
111
111
  returnTo?: string;
112
- /** OAuth scopes. All three tenancy tiers when omitted, so the user may
112
+ /** OAuth scopes. When omitted: `user:profile:read` (who connected) and
113
+ * the connections scopes of all three tenancy tiers, so the user may
113
114
  * grant from any space. */
114
115
  scopes?: string[];
115
116
  }
package/src/types.ts CHANGED
@@ -83,9 +83,9 @@ export interface ConnectButtonLogo {
83
83
  imageUrl: string;
84
84
  }
85
85
 
86
- /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.18.0. */
86
+ /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
87
87
  export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
88
- /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.18.0. */
88
+ /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
89
89
  export type ConnectButtonPlatform = ConnectButtonLogo;
90
90
 
91
91
  export type ConnectButtonVariant = "default" | "accent" | "block";
@@ -102,7 +102,7 @@ export interface ConnectButtonProps {
102
102
  * into a "+N" chip. Decoration only; the permission set decides what
103
103
  * One asks for. */
104
104
  logos?: ConnectButtonLogoInput[];
105
- /** @deprecated Renamed to `logos`; removed in 0.18.0. */
105
+ /** @deprecated Renamed to `logos`; removed in 0.20.0. */
106
106
  platforms?: ConnectButtonLogoInput[];
107
107
  /** Whether this user has a live grant, from your server. When set, it
108
108
  * decides the Connected state. When omitted, the button shows