@withone/connect 0.11.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 +58 -23
- 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 +12 -10
- package/dist/server/index.esm.js +12 -10
- package/dist/server/types.d.ts +6 -1
- 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 +33 -15
- 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 +12 -10
- package/src/server/types.ts +7 -1
- 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/server/index.esm.js
CHANGED
|
@@ -4,7 +4,8 @@ import { randomBytes, createHash } from 'node:crypto';
|
|
|
4
4
|
* The SDK reads them off the page URL when the tab comes home, so
|
|
5
5
|
* there is no completion page to build. */
|
|
6
6
|
const RETURN_STATUS_PARAM = "one_connect";
|
|
7
|
-
|
|
7
|
+
/** Why the flow failed, as a code (see ConnectFailureCode). */
|
|
8
|
+
const RETURN_ERROR_PARAM = "one_connect_error";
|
|
8
9
|
|
|
9
10
|
/** One's production API. Point `oneApiUrl` elsewhere for development. */
|
|
10
11
|
const DEFAULT_ONE_API_URL = "https://api.withone.ai";
|
|
@@ -180,10 +181,10 @@ function createOneConnect(config) {
|
|
|
180
181
|
/** One refresh in flight per user: two concurrent refreshes with the
|
|
181
182
|
* same refresh token trip One's reuse detection. */
|
|
182
183
|
const refreshing = new Map();
|
|
183
|
-
const returnUrl = (status,
|
|
184
|
+
const returnUrl = (status, code) => {
|
|
184
185
|
const url = new URL(returnTo, config.redirectUri);
|
|
185
186
|
url.searchParams.set(RETURN_STATUS_PARAM, status);
|
|
186
|
-
if (
|
|
187
|
+
if (code) url.searchParams.set(RETURN_ERROR_PARAM, code);
|
|
187
188
|
return url.toString();
|
|
188
189
|
};
|
|
189
190
|
const postToken = async body => {
|
|
@@ -260,17 +261,18 @@ function createOneConnect(config) {
|
|
|
260
261
|
const oauthError = params.get("error");
|
|
261
262
|
const cookieName = state ? txCookieName(state) : undefined;
|
|
262
263
|
const verifier = cookieName ? input.getCookie(cookieName) : undefined;
|
|
263
|
-
const fail = (
|
|
264
|
-
outcome,
|
|
264
|
+
const fail = (failure, message) => ({
|
|
265
|
+
outcome: failure === "declined" ? "declined" : "failed",
|
|
266
|
+
code: failure,
|
|
265
267
|
message,
|
|
266
|
-
redirectUrl: returnUrl("error",
|
|
268
|
+
redirectUrl: returnUrl("error", failure),
|
|
267
269
|
clearCookieName: cookieName
|
|
268
270
|
});
|
|
269
|
-
if (oauthError === "access_denied") return fail("declined", "
|
|
271
|
+
if (oauthError === "access_denied") return fail("declined", "The user cancelled on One's page.");
|
|
270
272
|
if (oauthError) return fail("failed", `One reported an error: ${oauthError}.`);
|
|
271
|
-
// The returned state names its own cookie. No cookie means a
|
|
272
|
-
// or
|
|
273
|
-
if (!code || !state || !verifier) return fail("
|
|
273
|
+
// The returned state names its own cookie. No cookie means a stale,
|
|
274
|
+
// foreign or forged state; the code is never exchanged in that case.
|
|
275
|
+
if (!code || !state || !verifier) return fail("expired", "The attempt expired, or its state cookie was missing.");
|
|
274
276
|
try {
|
|
275
277
|
const tokens = toTokens(await exchange(new URLSearchParams({
|
|
276
278
|
grant_type: "authorization_code",
|
package/dist/server/types.d.ts
CHANGED
|
@@ -101,10 +101,15 @@ export interface CompleteAuthorizationInput {
|
|
|
101
101
|
/** Reads a cookie by name from the incoming request. */
|
|
102
102
|
getCookie: (name: string) => string | undefined;
|
|
103
103
|
}
|
|
104
|
+
import type { ConnectFailureCode } from "../types";
|
|
105
|
+
export type { ConnectFailureCode };
|
|
104
106
|
export type AuthorizationOutcome = "connected" | "declined" | "failed";
|
|
105
107
|
export interface CompleteAuthorizationResult {
|
|
106
108
|
outcome: AuthorizationOutcome;
|
|
107
|
-
/**
|
|
109
|
+
/** Why it failed, as the code the browser receives. Only the code goes
|
|
110
|
+
* on the return URL; the browser shows fixed text for it. */
|
|
111
|
+
code?: ConnectFailureCode;
|
|
112
|
+
/** What happened, for your logs. Never put it in front of the user. */
|
|
108
113
|
message?: string;
|
|
109
114
|
/** Send the browser here with a 302; it carries `?one_connect=…`. */
|
|
110
115
|
redirectUrl: string;
|
package/dist/svelte.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("@withone/connect");exports.connectButton=function(
|
|
1
|
+
"use strict";var t=require("@withone/connect");exports.connectButton=function(n,e){const o=t.mountConnectButton(n,e);return{update:t=>o.update(t),destroy:()=>o.destroy()}};
|
package/dist/svelte.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Svelte compiler or dependency is involved:
|
|
6
6
|
*
|
|
7
7
|
* <div use:connectButton={{ authorizeUrl: "/api/one/authorize",
|
|
8
|
-
* platforms: ["stripe", "notion"],
|
|
8
|
+
* platforms: ["stripe", "notion"], connected: data.hasOneGrant,
|
|
9
9
|
* onSuccess: () => { ... } }} />
|
|
10
10
|
*/
|
|
11
11
|
import { type ConnectButtonProps } from "@withone/connect";
|
package/dist/svelte.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mountConnectButton as t
|
|
1
|
+
import{mountConnectButton as t}from"@withone/connect";function o(o,e){const n=t(o,e);return{update:t=>n.update(t),destroy:()=>n.destroy()}}export{o as connectButton};
|
package/dist/types.d.ts
CHANGED
|
@@ -8,32 +8,55 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Theme of One's hosted connect page. */
|
|
10
10
|
export type OneConnectTheme = "light" | "dark";
|
|
11
|
-
|
|
11
|
+
/** Theme of the button: fixed, or following the visitor's setting. */
|
|
12
|
+
export type ConnectButtonTheme = "light" | "dark" | "auto";
|
|
13
|
+
/**
|
|
14
|
+
* Why a flow ended without a grant. The callback route puts only this
|
|
15
|
+
* code on the return URL; the text shown for it is fixed in the SDK, so
|
|
16
|
+
* a crafted link can never put its own words in front of the user.
|
|
17
|
+
*
|
|
18
|
+
* - `declined`: the user cancelled on One's page.
|
|
19
|
+
* - `expired`: the attempt took too long or was started elsewhere.
|
|
20
|
+
* - `failed`: One could not complete the connection.
|
|
21
|
+
*/
|
|
22
|
+
export type ConnectFailureCode = "declined" | "expired" | "failed";
|
|
23
|
+
export interface OneConnectFlowOptions {
|
|
12
24
|
/** The app's own backend authorize route. Relative paths such as
|
|
13
25
|
* "/api/one/authorize" resolve against the page's origin. */
|
|
14
26
|
authorizeUrl: string;
|
|
15
27
|
/** Theme for One's hosted page. Carried on the URL fragment, which
|
|
16
28
|
* survives the redirect chain, so the backend forwards nothing. */
|
|
29
|
+
connectTheme?: OneConnectTheme;
|
|
30
|
+
/** @deprecated Renamed to `connectTheme`; removed in the next minor. */
|
|
17
31
|
appTheme?: OneConnectTheme;
|
|
18
|
-
/** The grant completed and the backend stored the tokens.
|
|
32
|
+
/** The grant completed and the backend stored the tokens. Fires once
|
|
33
|
+
* per page load, on the first flow still mounted when the tab
|
|
34
|
+
* returns. Treat it as a hint to refetch: your server is the truth. */
|
|
19
35
|
onSuccess?: () => void;
|
|
20
|
-
/** The flow ended without a grant
|
|
21
|
-
*
|
|
22
|
-
onError?: (message: string) => void;
|
|
36
|
+
/** The flow ended without a grant. `message` is fixed text for
|
|
37
|
+
* `code`, safe to show. */
|
|
38
|
+
onError?: (message: string, code: ConnectFailureCode) => void;
|
|
39
|
+
/** The user came back with the browser's Back button before finishing
|
|
40
|
+
* (the page was restored from the back-forward cache). */
|
|
41
|
+
onCancel?: () => void;
|
|
23
42
|
}
|
|
24
|
-
export interface
|
|
43
|
+
export interface OneConnectFlow {
|
|
25
44
|
/** Navigates the tab to One's hosted connect flow. */
|
|
26
45
|
open: () => void;
|
|
46
|
+
/** Swaps the options (callbacks, theme) without losing the flow. */
|
|
47
|
+
update: (options: OneConnectFlowOptions) => void;
|
|
48
|
+
/** Stops listening: callbacks no longer fire for this flow. */
|
|
49
|
+
destroy: () => void;
|
|
27
50
|
}
|
|
28
|
-
/** How the
|
|
29
|
-
* redirect, read off the page URL when the tab returns. */
|
|
51
|
+
/** How the flow ended, read off the page URL when the tab returns. */
|
|
30
52
|
export interface OneConnectReturn {
|
|
31
53
|
status: "success" | "error";
|
|
54
|
+
code?: ConnectFailureCode;
|
|
32
55
|
message?: string;
|
|
33
56
|
}
|
|
34
57
|
/**
|
|
35
58
|
* A connector chip on the button. Pass One's connector slug ("stripe",
|
|
36
|
-
* "google-calendar") and the SDK shows
|
|
59
|
+
* "google-calendar") and the SDK shows its logo and name; pass an
|
|
37
60
|
* object to override either.
|
|
38
61
|
*/
|
|
39
62
|
export type ConnectButtonPlatformInput = string | {
|
|
@@ -48,31 +71,50 @@ export interface ConnectButtonPlatform {
|
|
|
48
71
|
imageUrl: string;
|
|
49
72
|
}
|
|
50
73
|
export type ConnectButtonVariant = "default" | "accent" | "block";
|
|
74
|
+
export type ConnectButtonSize = "sm" | "md" | "lg";
|
|
51
75
|
export type ConnectButtonState = "idle" | "connecting" | "connected";
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* full-width card with a description and a "Secured by One" foot. */
|
|
60
|
-
variant?: ConnectButtonVariant;
|
|
61
|
-
/** Matches the host page, not One's page (that is connect.appTheme). */
|
|
62
|
-
theme?: OneConnectTheme;
|
|
63
|
-
/** Connector chips. The first three render; the rest fold into a "+N"
|
|
64
|
-
* chip, so that count only ever describes this list. */
|
|
76
|
+
/** One prop shape for every surface: React, Vue, Svelte, the custom
|
|
77
|
+
* element and `mountConnectButton`. */
|
|
78
|
+
export interface ConnectButtonProps {
|
|
79
|
+
/** The app's own backend authorize route; relative is fine. */
|
|
80
|
+
authorizeUrl: string;
|
|
81
|
+
/** Connector slugs, or objects that override the name or the logo.
|
|
82
|
+
* The first three draw as logos; the rest fold into a "+N" chip. */
|
|
65
83
|
platforms?: ConnectButtonPlatformInput[];
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
/** Whether this user has a live grant, from your server. When set, it
|
|
85
|
+
* decides the Connected state. When omitted, the button shows
|
|
86
|
+
* Connected only right after a successful return. */
|
|
87
|
+
connected?: boolean;
|
|
88
|
+
/** Not clickable, for example until terms are accepted. */
|
|
89
|
+
disabled?: boolean;
|
|
90
|
+
/** default = neutral, accent = your brand colour, block = a card with
|
|
91
|
+
* a description and a "Secured by One" foot. */
|
|
92
|
+
variant?: ConnectButtonVariant;
|
|
93
|
+
size?: ConnectButtonSize;
|
|
94
|
+
/** Stretches to the width of its container. */
|
|
95
|
+
fullWidth?: boolean;
|
|
96
|
+
/** Matches the host page. "auto" follows the visitor's setting. */
|
|
97
|
+
theme?: ConnectButtonTheme;
|
|
98
|
+
/** Theme of One's hosted page. */
|
|
99
|
+
connectTheme?: OneConnectTheme;
|
|
100
|
+
/** @deprecated Renamed to `connectTheme`; removed in the next minor. */
|
|
101
|
+
appTheme?: OneConnectTheme;
|
|
102
|
+
/** Fill of the accent variant; One's lime when omitted. The label is
|
|
103
|
+
* black or white, whichever reads better on it. */
|
|
69
104
|
accentColor?: string;
|
|
70
|
-
/**
|
|
105
|
+
/** "Connect your apps" unless set. */
|
|
106
|
+
label?: string;
|
|
107
|
+
/** "Connected" unless set. */
|
|
71
108
|
connectedLabel?: string;
|
|
109
|
+
/** Sub-line on the block variant. */
|
|
110
|
+
description?: string;
|
|
111
|
+
onSuccess?: () => void;
|
|
112
|
+
onError?: (message: string, code: ConnectFailureCode) => void;
|
|
113
|
+
onCancel?: () => void;
|
|
72
114
|
}
|
|
73
115
|
export interface ConnectButtonHandle {
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
/**
|
|
116
|
+
/** Applies new props in place, keeping the button's state. */
|
|
117
|
+
update: (props: ConnectButtonProps) => void;
|
|
118
|
+
/** Removes the button and stops its callbacks. */
|
|
77
119
|
destroy: () => void;
|
|
78
120
|
}
|
package/dist/vue.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("vue"),t=require("@withone/connect");const n=e.defineComponent({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},
|
|
1
|
+
"use strict";var e=require("vue"),t=require("@withone/connect");const n={type:Boolean,default:void 0},o=e.defineComponent({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},platforms:{type:Array,default:void 0},connected:n,disabled:n,variant:{type:String,default:void 0},size:{type:String,default:void 0},fullWidth:n,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(n,{emit:o}){const r=e.ref(null);let i=null;const d=()=>({...n,onSuccess:()=>o("success"),onError:(e,t)=>o("error",e,t),onCancel:()=>o("cancel")});return e.onMounted(()=>{r.value&&(i=t.mountConnectButton(r.value,d()))}),e.watch(()=>({...n}),()=>{var e;return null===(e=i)||void 0===e?void 0:e.update(d())},{deep:!0}),e.onBeforeUnmount(()=>{var e;null===(e=i)||void 0===e||e.destroy(),i=null}),()=>e.h("div",{ref:r})}});exports.ConnectButton=o;
|
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",
|
|
@@ -126,32 +126,50 @@ The routes mint `state` and PKCE, keep them in a per-flow httpOnly cookie
|
|
|
126
126
|
(`one_tx_<state>`, SameSite=Lax, 30 minutes), verify the state on return,
|
|
127
127
|
exchange the code with the secret over HTTP Basic, store both tokens, and
|
|
128
128
|
redirect to `/` with `?one_connect=success` or
|
|
129
|
-
`?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
|
|
130
131
|
`createOneConnect` to land somewhere else.
|
|
131
132
|
|
|
132
133
|
## 5 - The button
|
|
133
134
|
|
|
134
135
|
```tsx
|
|
135
|
-
import { ConnectButton } from "@withone/connect/react";
|
|
136
|
+
import { ConnectButton } from "@withone/connect/react"; // "use client" bundle: fine in a Server Component
|
|
136
137
|
|
|
137
138
|
<ConnectButton
|
|
138
139
|
authorizeUrl="/api/one/authorize"
|
|
139
|
-
platforms={["stripe", "google-calendar"]} // connector slugs; logos
|
|
140
|
-
|
|
141
|
-
|
|
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 */ }}
|
|
142
144
|
/>
|
|
143
145
|
```
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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.
|
|
155
173
|
|
|
156
174
|
## 6 - Calling One with the grant
|
|
157
175
|
|