@withone/connect 0.10.0 → 0.12.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 +144 -26
- package/dist/button.d.ts +20 -14
- package/dist/constants.d.ts +5 -1
- package/dist/flow.d.ts +18 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.esm.js +1 -1
- package/dist/platforms.d.ts +1 -1
- package/dist/react.cjs.js +2 -1
- package/dist/react.d.ts +20 -1
- package/dist/react.esm.js +2 -1
- package/dist/return.d.ts +5 -2
- package/dist/server/index.cjs.js +157 -45
- package/dist/server/index.d.ts +11 -3
- package/dist/server/index.esm.js +157 -46
- package/dist/server/oauth.d.ts +4 -0
- package/dist/server/types.d.ts +47 -4
- package/dist/svelte.cjs.js +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.esm.js +1 -1
- package/dist/types.d.ts +71 -29
- package/dist/vue.cjs.js +1 -1
- package/dist/vue.d.ts +71 -22
- package/dist/vue.esm.js +1 -1
- package/package.json +4 -1
- package/skills/one-connect/SKILL.md +55 -19
- package/src/button.ts +450 -295
- package/src/constants.ts +5 -1
- package/src/flow.ts +171 -0
- package/src/index.ts +17 -19
- package/src/platforms.ts +41 -6
- package/src/react.ts +119 -21
- package/src/return.ts +31 -9
- package/src/server/index.ts +188 -51
- package/src/server/oauth.ts +16 -0
- package/src/server/types.ts +49 -4
- package/src/svelte.ts +5 -13
- package/src/types.ts +73 -29
- package/src/vue.ts +42 -26
- package/dist/useOneConnect.d.ts +0 -13
- package/dist/wrapper-options.d.ts +0 -28
- package/src/useOneConnect.ts +0 -72
- package/src/wrapper-options.ts +0 -68
package/dist/vue.d.ts
CHANGED
|
@@ -1,35 +1,52 @@
|
|
|
1
1
|
import type { PropType } from "vue";
|
|
2
|
-
import { type ConnectButtonPlatformInput, type ConnectButtonVariant, type OneConnectTheme } from "@withone/connect";
|
|
2
|
+
import { type ConnectButtonPlatformInput, type ConnectButtonSize, type ConnectButtonTheme, type ConnectButtonVariant, type ConnectFailureCode, type OneConnectTheme } from "@withone/connect";
|
|
3
3
|
export declare const ConnectButton: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
4
|
authorizeUrl: {
|
|
5
5
|
type: StringConstructor;
|
|
6
6
|
required: true;
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
type: PropType<
|
|
8
|
+
platforms: {
|
|
9
|
+
type: PropType<ConnectButtonPlatformInput[]>;
|
|
10
10
|
default: undefined;
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
type:
|
|
12
|
+
connected: {
|
|
13
|
+
type: BooleanConstructor;
|
|
14
|
+
default: undefined;
|
|
15
|
+
};
|
|
16
|
+
disabled: {
|
|
17
|
+
type: BooleanConstructor;
|
|
14
18
|
default: undefined;
|
|
15
19
|
};
|
|
16
20
|
variant: {
|
|
17
21
|
type: PropType<ConnectButtonVariant>;
|
|
18
22
|
default: undefined;
|
|
19
23
|
};
|
|
24
|
+
size: {
|
|
25
|
+
type: PropType<ConnectButtonSize>;
|
|
26
|
+
default: undefined;
|
|
27
|
+
};
|
|
28
|
+
fullWidth: {
|
|
29
|
+
type: BooleanConstructor;
|
|
30
|
+
default: undefined;
|
|
31
|
+
};
|
|
20
32
|
theme: {
|
|
33
|
+
type: PropType<ConnectButtonTheme>;
|
|
34
|
+
default: undefined;
|
|
35
|
+
};
|
|
36
|
+
connectTheme: {
|
|
21
37
|
type: PropType<OneConnectTheme>;
|
|
22
38
|
default: undefined;
|
|
23
39
|
};
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
/** @deprecated use connectTheme */
|
|
41
|
+
appTheme: {
|
|
42
|
+
type: PropType<OneConnectTheme>;
|
|
26
43
|
default: undefined;
|
|
27
44
|
};
|
|
28
|
-
|
|
45
|
+
accentColor: {
|
|
29
46
|
type: StringConstructor;
|
|
30
47
|
default: undefined;
|
|
31
48
|
};
|
|
32
|
-
|
|
49
|
+
label: {
|
|
33
50
|
type: StringConstructor;
|
|
34
51
|
default: undefined;
|
|
35
52
|
};
|
|
@@ -37,41 +54,63 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
37
54
|
type: StringConstructor;
|
|
38
55
|
default: undefined;
|
|
39
56
|
};
|
|
57
|
+
description: {
|
|
58
|
+
type: StringConstructor;
|
|
59
|
+
default: undefined;
|
|
60
|
+
};
|
|
40
61
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
41
62
|
[key: string]: any;
|
|
42
63
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
43
64
|
success: () => true;
|
|
44
|
-
error: (message: string) => boolean;
|
|
65
|
+
error: (message: string, code: ConnectFailureCode) => boolean;
|
|
66
|
+
cancel: () => true;
|
|
45
67
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
46
68
|
authorizeUrl: {
|
|
47
69
|
type: StringConstructor;
|
|
48
70
|
required: true;
|
|
49
71
|
};
|
|
50
|
-
|
|
51
|
-
type: PropType<
|
|
72
|
+
platforms: {
|
|
73
|
+
type: PropType<ConnectButtonPlatformInput[]>;
|
|
52
74
|
default: undefined;
|
|
53
75
|
};
|
|
54
|
-
|
|
55
|
-
type:
|
|
76
|
+
connected: {
|
|
77
|
+
type: BooleanConstructor;
|
|
78
|
+
default: undefined;
|
|
79
|
+
};
|
|
80
|
+
disabled: {
|
|
81
|
+
type: BooleanConstructor;
|
|
56
82
|
default: undefined;
|
|
57
83
|
};
|
|
58
84
|
variant: {
|
|
59
85
|
type: PropType<ConnectButtonVariant>;
|
|
60
86
|
default: undefined;
|
|
61
87
|
};
|
|
88
|
+
size: {
|
|
89
|
+
type: PropType<ConnectButtonSize>;
|
|
90
|
+
default: undefined;
|
|
91
|
+
};
|
|
92
|
+
fullWidth: {
|
|
93
|
+
type: BooleanConstructor;
|
|
94
|
+
default: undefined;
|
|
95
|
+
};
|
|
62
96
|
theme: {
|
|
97
|
+
type: PropType<ConnectButtonTheme>;
|
|
98
|
+
default: undefined;
|
|
99
|
+
};
|
|
100
|
+
connectTheme: {
|
|
63
101
|
type: PropType<OneConnectTheme>;
|
|
64
102
|
default: undefined;
|
|
65
103
|
};
|
|
66
|
-
|
|
67
|
-
|
|
104
|
+
/** @deprecated use connectTheme */
|
|
105
|
+
appTheme: {
|
|
106
|
+
type: PropType<OneConnectTheme>;
|
|
68
107
|
default: undefined;
|
|
69
108
|
};
|
|
70
|
-
|
|
109
|
+
accentColor: {
|
|
71
110
|
type: StringConstructor;
|
|
72
111
|
default: undefined;
|
|
73
112
|
};
|
|
74
|
-
|
|
113
|
+
label: {
|
|
75
114
|
type: StringConstructor;
|
|
76
115
|
default: undefined;
|
|
77
116
|
};
|
|
@@ -79,16 +118,26 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
79
118
|
type: StringConstructor;
|
|
80
119
|
default: undefined;
|
|
81
120
|
};
|
|
121
|
+
description: {
|
|
122
|
+
type: StringConstructor;
|
|
123
|
+
default: undefined;
|
|
124
|
+
};
|
|
82
125
|
}>> & Readonly<{
|
|
83
126
|
onSuccess?: (() => any) | undefined;
|
|
84
|
-
onError?: ((message: string) => any) | undefined;
|
|
127
|
+
onError?: ((message: string, code: ConnectFailureCode) => any) | undefined;
|
|
128
|
+
onCancel?: (() => any) | undefined;
|
|
85
129
|
}>, {
|
|
130
|
+
connected: boolean;
|
|
86
131
|
label: string;
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
disabled: boolean;
|
|
133
|
+
connectTheme: OneConnectTheme;
|
|
134
|
+
appTheme: OneConnectTheme;
|
|
89
135
|
platforms: ConnectButtonPlatformInput[];
|
|
136
|
+
variant: ConnectButtonVariant;
|
|
137
|
+
size: ConnectButtonSize;
|
|
138
|
+
theme: ConnectButtonTheme;
|
|
90
139
|
description: string;
|
|
91
|
-
|
|
140
|
+
fullWidth: boolean;
|
|
92
141
|
accentColor: string;
|
|
93
142
|
connectedLabel: string;
|
|
94
143
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/vue.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineComponent as e,ref as t,onMounted as
|
|
1
|
+
import{defineComponent as e,ref as t,onMounted as n,watch as o,onBeforeUnmount as r,h as i}from"vue";import{mountConnectButton as l}from"@withone/connect";const d={type:Boolean,default:void 0},a=e({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},platforms:{type:Array,default:void 0},connected:d,disabled:d,variant:{type:String,default:void 0},size:{type:String,default:void 0},fullWidth:d,theme:{type:String,default:void 0},connectTheme:{type:String,default:void 0},appTheme:{type:String,default:void 0},accentColor:{type:String,default:void 0},label:{type:String,default:void 0},connectedLabel:{type:String,default:void 0},description:{type:String,default:void 0}},emits:{success:()=>!0,error:(e,t)=>"string"==typeof e&&"string"==typeof t,cancel:()=>!0},setup(e,{emit:d}){const a=t(null);let u=null;const c=()=>({...e,onSuccess:()=>d("success"),onError:(e,t)=>d("error",e,t),onCancel:()=>d("cancel")});return n(()=>{a.value&&(u=l(a.value,c()))}),o(()=>({...e}),()=>{var e;return null===(e=u)||void 0===e?void 0:e.update(c())},{deep:!0}),r(()=>{var e;null===(e=u)||void 0===e||e.destroy(),u=null}),()=>i("div",{ref:a})}});export{a as ConnectButton};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withone/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -27,8 +27,11 @@
|
|
|
27
27
|
"@tsconfig/recommended": "^1.0.3",
|
|
28
28
|
"@types/node": "^22.15.0",
|
|
29
29
|
"@types/react": "^19.2.18",
|
|
30
|
+
"@types/react-dom": "^19.3.0",
|
|
31
|
+
"happy-dom": "^20.14.5",
|
|
30
32
|
"prettier": "^3.2.5",
|
|
31
33
|
"react": "^19.2.8",
|
|
34
|
+
"react-dom": "^19.3.0",
|
|
32
35
|
"rollup": "^4.12.0",
|
|
33
36
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
34
37
|
"tslib": "^2.6.2",
|
|
@@ -72,7 +72,9 @@ export const oneConnect = createOneConnect({
|
|
|
72
72
|
tokenStore: {
|
|
73
73
|
saveTokens: (userId, tokens) => /* write to this app's database, encrypted, keyed by its user */,
|
|
74
74
|
loadTokens: (userId) => /* read; null when the user never connected */,
|
|
75
|
-
clearTokens: (userId) => /* delete */,
|
|
75
|
+
clearTokens: (userId, failed) => /* delete; when `failed` is set, only if the stored refreshToken is still failed.refreshToken */,
|
|
76
|
+
// Required when the app runs more than one process (serverless, several instances, a worker):
|
|
77
|
+
withLock: (userId, run) => /* run() while holding a per-user lock all processes share, e.g. pg_advisory_xact_lock */,
|
|
76
78
|
},
|
|
77
79
|
});
|
|
78
80
|
```
|
|
@@ -81,6 +83,14 @@ export const oneConnect = createOneConnect({
|
|
|
81
83
|
id as the key. Implement the store on whatever the app already uses; do not
|
|
82
84
|
add a database for it.
|
|
83
85
|
|
|
86
|
+
One rotates the refresh token on every refresh and revokes the whole grant if
|
|
87
|
+
an old one is used again. So when two processes could refresh the same user,
|
|
88
|
+
`withLock` is not optional: without it, a web request and a worker refreshing
|
|
89
|
+
together disconnect the user. The README's "Tokens" section has a Postgres
|
|
90
|
+
version. For background work, call `refreshIfExpiring(userId, { withinMs })`:
|
|
91
|
+
every few minutes with a window of minutes to keep access tokens warm, and
|
|
92
|
+
once a day with a window of days so idle users' 30-day refresh tokens renew.
|
|
93
|
+
|
|
84
94
|
## 4 - The two routes
|
|
85
95
|
|
|
86
96
|
Next.js App Router (also Remix, SvelteKit, Hono, Bun: anything with the web `Request`):
|
|
@@ -116,32 +126,50 @@ The routes mint `state` and PKCE, keep them in a per-flow httpOnly cookie
|
|
|
116
126
|
(`one_tx_<state>`, SameSite=Lax, 30 minutes), verify the state on return,
|
|
117
127
|
exchange the code with the secret over HTTP Basic, store both tokens, and
|
|
118
128
|
redirect to `/` with `?one_connect=success` or
|
|
119
|
-
`?one_connect=error&
|
|
129
|
+
`?one_connect=error&one_connect_error=declined|expired|failed` (a code, never
|
|
130
|
+
free text: the SDK shows fixed text for it). Pass `returnTo` to
|
|
120
131
|
`createOneConnect` to land somewhere else.
|
|
121
132
|
|
|
122
133
|
## 5 - The button
|
|
123
134
|
|
|
124
135
|
```tsx
|
|
125
|
-
import { ConnectButton } from "@withone/connect/react";
|
|
136
|
+
import { ConnectButton } from "@withone/connect/react"; // "use client" bundle: fine in a Server Component
|
|
126
137
|
|
|
127
138
|
<ConnectButton
|
|
128
139
|
authorizeUrl="/api/one/authorize"
|
|
129
|
-
platforms={["stripe", "google-calendar"]} // connector slugs; logos
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
platforms={["stripe", "google-calendar"]} // connector slugs; logos from One's CDN, names from the slug
|
|
141
|
+
connected={hasGrant} // from the server (e.g. await oneConnect.isConnected(userId))
|
|
142
|
+
onSuccess={() => { /* refetch app state; the tokens are already stored */ }}
|
|
143
|
+
onError={(message, code) => { /* show message; code: declined | expired | failed */ }}
|
|
132
144
|
/>
|
|
133
145
|
```
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
Other props: `disabled`, `variant` ("default" | "accent" | "block"), `size`
|
|
148
|
+
("sm" | "md" | "lg"), `fullWidth`, `theme` ("light" | "dark" | "auto"),
|
|
149
|
+
`connectTheme` (One's page), `accentColor`, `label`, `connectedLabel`,
|
|
150
|
+
`description` (block), `onCancel` (user pressed Back on One's page).
|
|
151
|
+
|
|
152
|
+
Always pass `connected` from the server. Without it the button forgets after
|
|
153
|
+
a reload and asks the user to connect again.
|
|
154
|
+
|
|
155
|
+
Vue: `import { ConnectButton } from "@withone/connect/vue"` with the same props
|
|
156
|
+
in kebab case (`authorize-url`, `:connected`), and `@success`, `@error`,
|
|
157
|
+
`@cancel`. Svelte: `import { connectButton } from "@withone/connect/svelte"`
|
|
158
|
+
as `use:connectButton={{ authorizeUrl, platforms, connected, onSuccess }}`.
|
|
159
|
+
Anything else: `import "@withone/connect"` registers
|
|
160
|
+
`<one-connect-button authorize-url="/api/one/authorize" platforms="stripe, notion" connected>`,
|
|
161
|
+
which dispatches `success`, `error` (detail `{ message, code }`) and `cancel`.
|
|
162
|
+
A custom button in React: `const { open, status, error } = useOneConnect({ authorizeUrl })`
|
|
163
|
+
from `@withone/connect/react`. Elsewhere: `createConnectFlow({ authorizeUrl, onSuccess, onError }).open`.
|
|
164
|
+
|
|
165
|
+
The button renders in a shadow root with a constructed stylesheet, so it
|
|
166
|
+
works under a strict CSP. Allow `https://assets.withone.ai` in `img-src` for
|
|
167
|
+
the logos. Style it with `--one-connect-font`, `--one-connect-radius` and
|
|
168
|
+
`::part(button)`; do not wrap it in extra styling divs.
|
|
169
|
+
|
|
170
|
+
The flow is a same-tab redirect. The outcome is read once per page load:
|
|
171
|
+
every button shows it, and the callbacks fire once, on the first button
|
|
172
|
+
still mounted.
|
|
145
173
|
|
|
146
174
|
## 6 - Calling One with the grant
|
|
147
175
|
|
|
@@ -170,14 +198,22 @@ grant; the provider was never called. Do not retry. Any other `/v1` call:
|
|
|
170
198
|
`oneConnect.fetch(userId, "/connections", init)`.
|
|
171
199
|
|
|
172
200
|
`OneConnectError` codes: `not_connected` (no tokens stored), `refresh_failed`
|
|
173
|
-
(One
|
|
174
|
-
again), `request_failed` (One answered with
|
|
201
|
+
(One declared the grant dead: revoked, expired or reused; the tokens were
|
|
202
|
+
cleared; ask the user to connect again), `request_failed` (One answered with
|
|
203
|
+
an error or could not be reached; `status` carries it; during a refresh the
|
|
204
|
+
tokens are KEPT, so retry later rather than asking the user to reconnect).
|
|
205
|
+
|
|
206
|
+
To ask users for more tools later, edit the app's permission set in the
|
|
207
|
+
dashboard. The next Connect shows only the new tools as "needs one more
|
|
208
|
+
connection" and the callback stores the new pair; no code change.
|
|
175
209
|
|
|
176
210
|
## 7 - Rules
|
|
177
211
|
|
|
178
212
|
- Never put the secret in a client bundle, a log line or an error report.
|
|
179
213
|
- Store tokens encrypted, keyed by the app's user. Delete them when the user
|
|
180
|
-
is deleted. The client deletes them itself when
|
|
214
|
+
is deleted. The client deletes them itself only when One declares the
|
|
215
|
+
grant dead, and never deletes a newer pair saved in the meantime.
|
|
216
|
+
- Give the store `withLock` whenever more than one process can refresh.
|
|
181
217
|
- The registered redirect URI and `ONE_REDIRECT_URI` must be the same string.
|
|
182
218
|
- Do not build a completion page. The callback redirect is the completion.
|
|
183
219
|
- Do not write the OAuth steps by hand when the package exposes them.
|