@withone/connect 0.8.3 → 0.9.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 +158 -370
- package/dist/button.d.ts +9 -10
- package/dist/constants.d.ts +8 -4
- package/dist/index.cjs.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +1 -1
- package/dist/next.cjs.js +104 -0
- package/dist/next.d.ts +36 -0
- package/dist/next.esm.js +98 -0
- package/dist/node.cjs.js +69 -0
- package/dist/node.d.ts +33 -0
- package/dist/node.esm.js +67 -0
- package/dist/platforms.d.ts +21 -0
- package/dist/react.cjs.js +1 -1
- package/dist/react.d.ts +2 -19
- package/dist/react.esm.js +1 -1
- package/dist/return.d.ts +11 -0
- package/dist/server/index.cjs.js +349 -0
- package/dist/server/index.d.ts +32 -0
- package/dist/server/index.esm.js +344 -0
- package/dist/server/oauth.d.ts +24 -0
- package/dist/server/types.d.ts +136 -0
- package/dist/svelte.cjs.js +1 -1
- package/dist/svelte.d.ts +14 -4
- package/dist/svelte.esm.js +1 -1
- package/dist/types.d.ts +80 -0
- package/dist/useOneConnect.d.ts +13 -2
- package/dist/vue.cjs.js +1 -1
- package/dist/vue.d.ts +20 -18
- package/dist/vue.esm.js +1 -1
- package/dist/wrapper-options.d.ts +20 -15
- package/package.json +37 -15
- package/skills/one-connect/SKILL.md +194 -0
- package/src/button.ts +106 -157
- package/src/constants.ts +10 -4
- package/src/index.ts +17 -2
- package/src/next.ts +133 -0
- package/src/node.ts +107 -0
- package/src/platforms.ts +89 -0
- package/src/react.ts +23 -66
- package/src/return.ts +30 -0
- package/src/server/index.ts +382 -0
- package/src/server/oauth.ts +98 -0
- package/src/server/types.ts +155 -0
- package/src/svelte.ts +15 -15
- package/src/types.ts +87 -0
- package/src/useOneConnect.ts +36 -69
- package/src/vue.ts +23 -29
- package/src/wrapper-options.ts +46 -23
- package/dist/index.umd.js +0 -1
- package/dist/types/index.d.ts +0 -92
- package/src/react-types.d.ts +0 -23
- package/src/svelte-types.d.ts +0 -27
- package/src/types/index.d.ts +0 -92
- package/src/vue-types.d.ts +0 -16
package/src/types.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for @withone/connect.
|
|
3
|
+
*
|
|
4
|
+
* The browser half knows nothing about OAuth: it sends the tab to the
|
|
5
|
+
* app's own authorize route and reports how the flow ended when the tab
|
|
6
|
+
* comes back. The server half (`@withone/connect/server`) owns state,
|
|
7
|
+
* PKCE, the code exchange, refresh and every call made with the grant.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Theme of One's hosted connect page. */
|
|
11
|
+
export type OneConnectTheme = "light" | "dark";
|
|
12
|
+
|
|
13
|
+
export interface OneConnectOptions {
|
|
14
|
+
/** The app's own backend authorize route. Relative paths such as
|
|
15
|
+
* "/api/one/authorize" resolve against the page's origin. */
|
|
16
|
+
authorizeUrl: string;
|
|
17
|
+
/** Theme for One's hosted page. Carried on the URL fragment, which
|
|
18
|
+
* survives the redirect chain, so the backend forwards nothing. */
|
|
19
|
+
appTheme?: OneConnectTheme;
|
|
20
|
+
/** The grant completed and the backend stored the tokens. */
|
|
21
|
+
onSuccess?: () => void;
|
|
22
|
+
/** The flow ended without a grant: the user declined, the attempt
|
|
23
|
+
* expired, or the exchange failed. `message` is safe to show. */
|
|
24
|
+
onError?: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OneConnectHandle {
|
|
28
|
+
/** Navigates the tab to One's hosted connect flow. */
|
|
29
|
+
open: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** How the app's callback route reports the outcome on its final
|
|
33
|
+
* redirect, read off the page URL when the tab returns. */
|
|
34
|
+
export interface OneConnectReturn {
|
|
35
|
+
status: "success" | "error";
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A connector chip on the button. Pass One's connector slug ("stripe",
|
|
41
|
+
* "google-calendar") and the SDK shows the logo and the name; pass an
|
|
42
|
+
* object to override either.
|
|
43
|
+
*/
|
|
44
|
+
export type ConnectButtonPlatformInput =
|
|
45
|
+
| string
|
|
46
|
+
| { slug?: string; name?: string; imageUrl?: string };
|
|
47
|
+
|
|
48
|
+
/** A normalized chip: what the button actually draws. */
|
|
49
|
+
export interface ConnectButtonPlatform {
|
|
50
|
+
slug: string;
|
|
51
|
+
name: string;
|
|
52
|
+
imageUrl: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ConnectButtonVariant = "default" | "accent" | "block";
|
|
56
|
+
export type ConnectButtonState = "idle" | "connecting" | "connected";
|
|
57
|
+
|
|
58
|
+
export interface ConnectButtonOptions {
|
|
59
|
+
/** Everything the flow needs; the button wires open() and the
|
|
60
|
+
* Connecting and Connected states around your callbacks. */
|
|
61
|
+
connect: OneConnectOptions;
|
|
62
|
+
/** "Connect your apps" unless overridden. */
|
|
63
|
+
label?: string;
|
|
64
|
+
/** default = neutral pill; accent = brand-colored pill; block =
|
|
65
|
+
* full-width card with a description and a "Secured by One" foot. */
|
|
66
|
+
variant?: ConnectButtonVariant;
|
|
67
|
+
/** Matches the host page, not One's page (that is connect.appTheme). */
|
|
68
|
+
theme?: OneConnectTheme;
|
|
69
|
+
/** Connector chips. The first three render; the rest fold into the
|
|
70
|
+
* "+N" chip together with moreCount. */
|
|
71
|
+
platforms?: ConnectButtonPlatformInput[];
|
|
72
|
+
/** Extra count for the "+N" chip, e.g. 274 for "the whole catalog". */
|
|
73
|
+
moreCount?: number;
|
|
74
|
+
/** Sub-line on the block variant, shown while idle. */
|
|
75
|
+
description?: string;
|
|
76
|
+
/** Fill of the accent variant; One's lime when omitted. */
|
|
77
|
+
accentColor?: string;
|
|
78
|
+
/** Label for the connected state. */
|
|
79
|
+
connectedLabel?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ConnectButtonHandle {
|
|
83
|
+
/** Override the visual state by hand. */
|
|
84
|
+
setState: (state: ConnectButtonState) => void;
|
|
85
|
+
/** Remove the button. */
|
|
86
|
+
destroy: () => void;
|
|
87
|
+
}
|
package/src/useOneConnect.ts
CHANGED
|
@@ -1,105 +1,72 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
THEME_PARAM,
|
|
5
|
-
} from "./constants";
|
|
6
|
-
import type { OneConnectHandle, OneConnectProps } from "./types";
|
|
1
|
+
import { THEME_PARAM } from "./constants";
|
|
2
|
+
import { hasReturnParams, parseReturn, stripReturnParams } from "./return";
|
|
3
|
+
import type { OneConnectHandle, OneConnectOptions } from "./types";
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** The return leg is a page LOAD — only the first hook instance on the
|
|
12
|
-
* page consumes it. */
|
|
5
|
+
/** The return leg is a page load, so it is delivered exactly once, to
|
|
6
|
+
* the first instance that reads it. */
|
|
13
7
|
let returnConsumed = false;
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
let resultDelivered = false;
|
|
9
|
+
const DEFAULT_ERROR = "The connection was not completed.";
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Wires the connect flow to any element. A plain function rather than a
|
|
13
|
+
* React hook, so it works from every framework: call it once where the
|
|
14
|
+
* button lives and pass `open` to a click.
|
|
15
|
+
*
|
|
16
|
+
* The flow is a full-page redirect: the tab goes to the app's authorize
|
|
17
|
+
* route, on to One's hosted page, back to the app's callback route, and
|
|
18
|
+
* home. On the way home the callback route appends `?one_connect=…` to
|
|
19
|
+
* the URL it redirects to; this function reads that on load, fires
|
|
20
|
+
* onSuccess or onError once, and removes the params from the address bar.
|
|
21
|
+
*/
|
|
22
|
+
export const useOneConnect = (options: OneConnectOptions): OneConnectHandle => {
|
|
23
|
+
const buildAuthorizeUrl = (): string => {
|
|
23
24
|
try {
|
|
24
|
-
// Relative paths ("/api/one/authorize") resolve against the host
|
|
25
|
-
// page — the natural thing to write on a <one-connect-button>.
|
|
26
25
|
const url = new URL(
|
|
27
|
-
|
|
26
|
+
options.authorizeUrl,
|
|
28
27
|
typeof window === "undefined" ? undefined : window.location.origin,
|
|
29
28
|
);
|
|
30
|
-
if (
|
|
29
|
+
if (options.appTheme) url.hash = `${THEME_PARAM}=${options.appTheme}`;
|
|
31
30
|
return url.toString();
|
|
32
31
|
} catch {
|
|
33
|
-
return
|
|
32
|
+
return options.authorizeUrl;
|
|
34
33
|
}
|
|
35
34
|
};
|
|
36
35
|
|
|
37
|
-
// Full-page hosted flow, Stripe-Checkout style: same tab, One's own
|
|
38
|
-
// domain (first-party cookies — works in every browser). The app's
|
|
39
|
-
// callback redirect brings the user home; detection below picks it
|
|
40
|
-
// up on the next load.
|
|
41
36
|
const open = () => {
|
|
42
37
|
if (typeof window === "undefined") return;
|
|
43
|
-
window.location.assign(
|
|
38
|
+
window.location.assign(buildAuthorizeUrl());
|
|
44
39
|
};
|
|
45
40
|
|
|
46
|
-
// Nothing to dismantle in the full-page flow — kept so hosts (and the
|
|
47
|
-
// button's destroy path) can call it unconditionally.
|
|
48
|
-
const close = (_options?: { keepResult?: boolean }) => {};
|
|
49
|
-
|
|
50
|
-
// Return detection: the app's callback redirected the TAB to a URL
|
|
51
|
-
// carrying ?one_connect=… — deliver it once and scrub the params so a
|
|
52
|
-
// refresh doesn't re-fire.
|
|
53
41
|
if (typeof window !== "undefined" && !returnConsumed) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
if (returnStatus === "success" || returnStatus === "error") {
|
|
42
|
+
const outcome = parseReturn(window.location.search);
|
|
43
|
+
if (outcome) {
|
|
57
44
|
returnConsumed = true;
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// hydration completes — silently undoing a one-shot replaceState.
|
|
63
|
-
// So scrub now, then re-check a few beats later and scrub again if
|
|
64
|
-
// the params were restored.
|
|
45
|
+
// Frameworks that manage the history themselves (the Next.js App
|
|
46
|
+
// Router) sync the address bar back to their own URL when hydration
|
|
47
|
+
// completes, undoing a single replaceState. Scrub now and re-check a
|
|
48
|
+
// few beats later.
|
|
65
49
|
const scrub = () => {
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
!params.has(RETURN_STATUS_PARAM) &&
|
|
69
|
-
!params.has(RETURN_MESSAGE_PARAM)
|
|
70
|
-
)
|
|
71
|
-
return;
|
|
72
|
-
params.delete(RETURN_STATUS_PARAM);
|
|
73
|
-
params.delete(RETURN_MESSAGE_PARAM);
|
|
50
|
+
if (!hasReturnParams(window.location.search)) return;
|
|
74
51
|
window.history.replaceState(
|
|
75
52
|
null,
|
|
76
53
|
"",
|
|
77
|
-
window.location
|
|
78
|
-
(params.toString() ? `?${params.toString()}` : "") +
|
|
79
|
-
window.location.hash,
|
|
54
|
+
stripReturnParams(window.location),
|
|
80
55
|
);
|
|
81
56
|
};
|
|
82
57
|
scrub();
|
|
83
58
|
for (const delay of [50, 500, 2000]) window.setTimeout(scrub, delay);
|
|
84
|
-
|
|
85
|
-
// ("You're all set" + countdown / the failure screen) before
|
|
86
|
-
// sending the user home — painting a second card would
|
|
87
|
-
// double-announce. Callbacks fire so the app updates its state.
|
|
59
|
+
|
|
88
60
|
window.setTimeout(() => {
|
|
89
|
-
if (resultDelivered) return;
|
|
90
|
-
resultDelivered = true;
|
|
91
61
|
try {
|
|
92
|
-
if (
|
|
93
|
-
else
|
|
94
|
-
props.onError?.(
|
|
95
|
-
returnMessage ?? "The connection was not completed.",
|
|
96
|
-
);
|
|
62
|
+
if (outcome.status === "success") options.onSuccess?.();
|
|
63
|
+
else options.onError?.(outcome.message ?? DEFAULT_ERROR);
|
|
97
64
|
} catch {
|
|
98
|
-
/*
|
|
65
|
+
/* the app's own callback threw; not ours to handle */
|
|
99
66
|
}
|
|
100
67
|
}, 0);
|
|
101
68
|
}
|
|
102
69
|
}
|
|
103
70
|
|
|
104
|
-
return { open
|
|
71
|
+
return { open };
|
|
105
72
|
};
|
package/src/vue.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vue 3
|
|
2
|
+
* Vue 3: `import { ConnectButton } from "@withone/connect/vue"`.
|
|
3
3
|
*
|
|
4
4
|
* <ConnectButton
|
|
5
5
|
* authorize-url="/api/one/authorize"
|
|
6
|
-
* :platforms="[
|
|
6
|
+
* :platforms="['stripe', 'notion']"
|
|
7
7
|
* @success="onConnected"
|
|
8
8
|
* />
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Vue is an optional peer dependency of this subpath only.
|
|
11
11
|
*/
|
|
12
12
|
import {
|
|
13
13
|
defineComponent,
|
|
@@ -19,23 +19,29 @@ import {
|
|
|
19
19
|
} from "vue";
|
|
20
20
|
import type { PropType } from "vue";
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
import {
|
|
23
|
+
mountConnectButton,
|
|
24
|
+
optionsFromProps,
|
|
25
|
+
propsIdentity,
|
|
26
|
+
type ConnectButtonHandle,
|
|
27
|
+
type ConnectButtonPlatformInput,
|
|
28
|
+
type ConnectButtonVariant,
|
|
29
|
+
type OneConnectTheme,
|
|
30
|
+
} from "@withone/connect";
|
|
25
31
|
|
|
26
32
|
export const ConnectButton = defineComponent({
|
|
27
33
|
name: "OneConnectButton",
|
|
28
34
|
props: {
|
|
29
35
|
authorizeUrl: { type: String, required: true },
|
|
30
|
-
appTheme: { type: String as PropType<
|
|
36
|
+
appTheme: { type: String as PropType<OneConnectTheme>, default: undefined },
|
|
31
37
|
label: { type: String, default: undefined },
|
|
32
38
|
variant: {
|
|
33
|
-
type: String as PropType<
|
|
39
|
+
type: String as PropType<ConnectButtonVariant>,
|
|
34
40
|
default: undefined,
|
|
35
41
|
},
|
|
36
|
-
theme: { type: String as PropType<
|
|
42
|
+
theme: { type: String as PropType<OneConnectTheme>, default: undefined },
|
|
37
43
|
platforms: {
|
|
38
|
-
type: Array as PropType<
|
|
44
|
+
type: Array as PropType<ConnectButtonPlatformInput[]>,
|
|
39
45
|
default: undefined,
|
|
40
46
|
},
|
|
41
47
|
moreCount: { type: Number, default: undefined },
|
|
@@ -43,7 +49,10 @@ export const ConnectButton = defineComponent({
|
|
|
43
49
|
accentColor: { type: String, default: undefined },
|
|
44
50
|
connectedLabel: { type: String, default: undefined },
|
|
45
51
|
},
|
|
46
|
-
emits:
|
|
52
|
+
emits: {
|
|
53
|
+
success: () => true,
|
|
54
|
+
error: (message: string) => typeof message === "string",
|
|
55
|
+
},
|
|
47
56
|
setup(props, { emit }) {
|
|
48
57
|
const container = ref<HTMLElement | null>(null);
|
|
49
58
|
let handle: ConnectButtonHandle | null = null;
|
|
@@ -54,33 +63,18 @@ export const ConnectButton = defineComponent({
|
|
|
54
63
|
if (!container.value) return;
|
|
55
64
|
handle = mountConnectButton(
|
|
56
65
|
container.value,
|
|
57
|
-
|
|
66
|
+
optionsFromProps(
|
|
58
67
|
{ ...props },
|
|
59
68
|
{
|
|
60
69
|
onSuccess: () => emit("success"),
|
|
61
|
-
onError: (
|
|
62
|
-
onClose: () => emit("close"),
|
|
70
|
+
onError: (message) => emit("error", message),
|
|
63
71
|
},
|
|
64
72
|
),
|
|
65
73
|
);
|
|
66
74
|
};
|
|
67
75
|
|
|
68
76
|
onMounted(mount);
|
|
69
|
-
watch(
|
|
70
|
-
() => [
|
|
71
|
-
props.authorizeUrl,
|
|
72
|
-
props.appTheme,
|
|
73
|
-
props.label,
|
|
74
|
-
props.variant,
|
|
75
|
-
props.theme,
|
|
76
|
-
JSON.stringify(props.platforms ?? []),
|
|
77
|
-
props.moreCount,
|
|
78
|
-
props.description,
|
|
79
|
-
props.accentColor,
|
|
80
|
-
props.connectedLabel,
|
|
81
|
-
],
|
|
82
|
-
mount,
|
|
83
|
-
);
|
|
77
|
+
watch(() => propsIdentity({ ...props }), mount);
|
|
84
78
|
onBeforeUnmount(() => {
|
|
85
79
|
handle?.destroy();
|
|
86
80
|
handle = null;
|
package/src/wrapper-options.ts
CHANGED
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ConnectButtonOptions,
|
|
3
|
+
ConnectButtonPlatformInput,
|
|
4
|
+
ConnectButtonVariant,
|
|
5
|
+
OneConnectTheme,
|
|
6
|
+
} from "./types";
|
|
2
7
|
|
|
3
|
-
/** The flat prop shape every framework wrapper exposes
|
|
4
|
-
* Vue props, Svelte action options
|
|
5
|
-
*
|
|
6
|
-
export interface
|
|
8
|
+
/** The flat prop shape every framework wrapper exposes: React props,
|
|
9
|
+
* Vue props, the Svelte action's options. One place turns it into the
|
|
10
|
+
* core mountConnectButton options. */
|
|
11
|
+
export interface ConnectButtonProps {
|
|
12
|
+
/** The app's own backend authorize route; relative is fine. */
|
|
7
13
|
authorizeUrl: string;
|
|
8
|
-
|
|
14
|
+
/** Theme of One's hosted page. */
|
|
15
|
+
appTheme?: OneConnectTheme;
|
|
9
16
|
onSuccess?: () => void;
|
|
10
|
-
onError?: (
|
|
11
|
-
onClose?: () => void;
|
|
17
|
+
onError?: (message: string) => void;
|
|
12
18
|
label?: string;
|
|
13
|
-
variant?:
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
variant?: ConnectButtonVariant;
|
|
20
|
+
/** Matches the host page. */
|
|
21
|
+
theme?: OneConnectTheme;
|
|
22
|
+
/** Connector slugs, or objects to override name or logo. */
|
|
23
|
+
platforms?: ConnectButtonPlatformInput[];
|
|
16
24
|
moreCount?: number;
|
|
17
25
|
description?: string;
|
|
18
26
|
accentColor?: string;
|
|
19
27
|
connectedLabel?: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
export interface ConnectButtonCallbacks {
|
|
31
|
+
onSuccess?: () => void;
|
|
32
|
+
onError?: (message: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function optionsFromProps(
|
|
36
|
+
props: ConnectButtonProps,
|
|
37
|
+
callbacks: ConnectButtonCallbacks = props,
|
|
29
38
|
): ConnectButtonOptions {
|
|
30
|
-
const cb = callbacks ?? props;
|
|
31
39
|
return {
|
|
32
40
|
connect: {
|
|
33
|
-
|
|
41
|
+
authorizeUrl: props.authorizeUrl,
|
|
34
42
|
appTheme: props.appTheme,
|
|
35
|
-
onSuccess: () =>
|
|
36
|
-
onError: (
|
|
37
|
-
onClose: () => cb.onClose?.(),
|
|
43
|
+
onSuccess: () => callbacks.onSuccess?.(),
|
|
44
|
+
onError: (message) => callbacks.onError?.(message),
|
|
38
45
|
},
|
|
39
46
|
label: props.label,
|
|
40
47
|
variant: props.variant,
|
|
@@ -46,3 +53,19 @@ export function optionsFromWrapperProps(
|
|
|
46
53
|
connectedLabel: props.connectedLabel,
|
|
47
54
|
};
|
|
48
55
|
}
|
|
56
|
+
|
|
57
|
+
/** The inputs whose change should remount the button. */
|
|
58
|
+
export function propsIdentity(props: ConnectButtonProps): string {
|
|
59
|
+
return JSON.stringify([
|
|
60
|
+
props.authorizeUrl,
|
|
61
|
+
props.appTheme,
|
|
62
|
+
props.label,
|
|
63
|
+
props.variant,
|
|
64
|
+
props.theme,
|
|
65
|
+
props.platforms ?? [],
|
|
66
|
+
props.moreCount,
|
|
67
|
+
props.description,
|
|
68
|
+
props.accentColor,
|
|
69
|
+
props.connectedLabel,
|
|
70
|
+
]);
|
|
71
|
+
}
|
package/dist/index.umd.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const e="one_connect",t="one_connect_message";let n=!1;const o=o=>{let i=!1;if("undefined"!=typeof window&&!n){const l=new URLSearchParams(window.location.search),r=l.get(e);if("success"===r||"error"===r){var c;n=!0;const s=null!==(c=l.get(t))&&void 0!==c?c:void 0,a=()=>{const n=new URLSearchParams(window.location.search);(n.has(e)||n.has(t))&&(n.delete(e),n.delete(t),window.history.replaceState(null,"",window.location.pathname+(n.toString()?`?${n.toString()}`:"")+window.location.hash))};a();for(const e of[50,500,2e3])window.setTimeout(a,e);window.setTimeout(()=>{if(!i){i=!0;try{var e,t;"success"===r?null===(e=o.onSuccess)||void 0===e||e.call(o):null===(t=o.onError)||void 0===t||t.call(o,null!=s?s:"The connection was not completed.")}catch{}}},0)}}return{open:()=>{"undefined"!=typeof window&&window.location.assign((()=>{try{const e=new URL(o.authorize.url,"undefined"==typeof window?void 0:window.location.origin);return o.appTheme&&(e.hash=`one_theme=${o.appTheme}`),e.toString()}catch{return o.authorize.url}})())},close:e=>{}}};function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var o=n.call(e,t);if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const c="one-connect-button-styles",l="cubic-bezier(.2,.9,.25,1)",r={btnBg:"#0A0C0B",btnFg:"#ffffff",surface:"#ffffff",fg:"#0A0C0B",muted:"#6B7280",line:"#D1D5DB",shadow:"0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)"},s={btnBg:"#F2F5F3",btnFg:"#0A0C0B",surface:"#101312",fg:"#F2F5F3",muted:"#8A938E",line:"#2A302D",shadow:"0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)"},a="#CCFF00",d="#3FE3A5",p="#0A0C0B";function u(e){const t=e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return t?`https://assets.withone.ai/connectors/${t}.svg`:""}const h=(e,t)=>{var n,i;!function(){if("undefined"==typeof document||document.getElementById(c))return;const e=document.createElement("style");e.id=c,e.textContent=[`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${l},box-shadow .2s ${l},opacity .2s ${l}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${d};outline-offset:3px}`,".owcb[disabled]{cursor:default;opacity:.75;transform:none}",".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${l};position:relative}`,".owcb-chip:first-child{margin-left:0}",".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",".owcb:hover .owcb-chip{margin-left:-2px}",".owcb:hover .owcb-chip:first-child{margin-left:0}",".owcb-label{white-space:nowrap}",`.owcb-arrow{opacity:.5;transition:transform .2s ${l},opacity .2s ${l}}`,".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",".owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}","@keyframes owcb-spin{to{transform:rotate(360deg)}}",".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}","@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}"].join("\n"),document.head.appendChild(e)}();const h="dark"===t.theme?s:r,m=null!==(n=t.variant)&&void 0!==n?n:"default",f=null!==(i=t.label)&&void 0!==i?i:"Connect your apps";let b="idle",w=!1;const g=o({...t.connect,onSuccess:()=>{var e,n;y("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;y("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;y("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),v=document.createElement("button");v.type="button",v.className="block"===m?"owcb owcb-block":"owcb",v.addEventListener("click",()=>{"idle"!==b&&"connected"!==b||(y("connecting"),g.open())});const x=()=>{var e,n,o;const i="accent"===m,c="block"===m,l=c?h.surface:i?null!==(e=t.accentColor)&&void 0!==e?e:a:h.btnBg,r=c?h.fg:i?p:h.btnFg,s=c?h.surface:l,w=p;v.style.background="connected"!==b||c?l:d,v.style.color="connected"!==b||c?r:p,v.style.boxShadow=c?`inset 0 0 0 1px ${h.line}`:"connected"===b?"none":i?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:a}38`:h.shadow,v.disabled="connecting"===b,v.innerHTML="";const g=function(e,t,n){var o,i;const c=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===c.length&&!e.moreCount)return null;const l=document.createElement("span");l.className="owcb-stack",l.setAttribute("aria-hidden","true");for(const e of c.slice(0,3)){var r;const n=document.createElement("span");n.className="owcb-chip",n.style.background="#ffffff",n.style.boxShadow=`0 0 0 1.5px ${t}`;const o=()=>{n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=p},i=null!==(r=e.imageUrl)&&void 0!==r?r:u(e.name);if(i){const e=document.createElement("img");e.alt="",e.src=i,e.addEventListener("error",()=>{e.remove(),o()}),n.appendChild(e)}else o();l.appendChild(n)}const s=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,c.length-3);if(s>0){const e=document.createElement("span");e.className="owcb-chip owcb-more",e.style.background="#ffffff",e.style.boxShadow=`0 0 0 1.5px ${t}`,e.style.color=n,e.textContent=`+${s}`,l.appendChild(e)}return l}(t,s,w);if(c){var x;const e=document.createElement("span");e.className="owcb-block-top";const n=document.createElement("span");if(n.className="owcb-block-title",n.textContent="connecting"===b?"Connecting…":"connected"===b?null!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":f,e.appendChild(n),"connecting"===b){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(v.appendChild(e),t.description&&"idle"===b){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=h.muted,e.textContent=t.description,v.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=h.muted,o.style.borderTop=`1px solid ${h.line}`,o.innerHTML=`<span style="font-size:11px;color:${h.muted}">Secured by</span><span style="display:inline-flex;color:${h.fg}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${h.fg}">one</span>`,void v.appendChild(o)}if("connecting"===b){const e=document.createElement("span");e.className="owcb-spinner",v.appendChild(e)}else if("connected"===b){const e=document.createElement("span");e.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}else g&&v.appendChild(g);const y=document.createElement("span");if(y.className="owcb-label",y.textContent="connecting"===b?"Connecting…":"connected"===b?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":f,v.appendChild(y),"idle"===b){const e=document.createElement("span");e.innerHTML='<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}},y=e=>{w||(b=e,x())};return x(),e.appendChild(v),{setState:y,destroy:()=>{w=!0,g.close({keepResult:!0}),v.remove()}}};function m(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),i(this,"onSuccess",null),i(this,"onError",null),i(this,"onClose",null),i(this,"handle",null)}connectedCallback(){this.mount()}disconnectedCallback(){var e;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null}attributeChangedCallback(){this.isConnected&&this.handle&&this.mount()}mount(){var e,t,n,o,i,c,l,r;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const s=this.getAttribute("authorize-url");if(!s)return;let a;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(a=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=h(this,{connect:{authorize:{url:s},appTheme:null!==(t=this.getAttribute("app-theme"))&&void 0!==t?t:void 0,onSuccess:()=>{var e;null===(e=this.onSuccess)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("success"))},onError:e=>{var t;null===(t=this.onError)||void 0===t||t.call(this,e),this.dispatchEvent(new CustomEvent("error",{detail:e}))},onClose:()=>{var e;null===(e=this.onClose)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("close"))}},label:null!==(n=this.getAttribute("label"))&&void 0!==n?n:void 0,variant:null!==(o=this.getAttribute("variant"))&&void 0!==o?o:void 0,theme:null!==(i=this.getAttribute("theme"))&&void 0!==i?i:void 0,platforms:a,moreCount:Number.isFinite(u)?u:void 0,description:null!==(c=this.getAttribute("description"))&&void 0!==c?c:void 0,accentColor:null!==(l=this.getAttribute("accent-color"))&&void 0!==l?l:void 0,connectedLabel:null!==(r=this.getAttribute("connected-label"))&&void 0!==r?r:void 0})}}i(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}m(),exports.mountConnectButton=h,exports.registerConnectButton=m,exports.useOneConnect=o;
|
package/dist/types/index.d.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public types for @withone/connect.
|
|
3
|
-
*
|
|
4
|
-
* The SDK deliberately knows nothing about OAuth internals: state, PKCE
|
|
5
|
-
* and the client secret live on the consumer's backend (see README).
|
|
6
|
-
* The SDK only sends the tab to One's hosted connect page and reports
|
|
7
|
-
* how the flow ended when the user comes back.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** How a completed flow reports back on the return URL. */
|
|
11
|
-
export interface OneConnectResult {
|
|
12
|
-
status: "success" | "error";
|
|
13
|
-
/** Human-readable detail for the error case. */
|
|
14
|
-
message?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface OneConnectProps {
|
|
18
|
-
/**
|
|
19
|
-
* The consumer's OWN backend route that starts the flow. It must
|
|
20
|
-
* generate `state` + PKCE, set them in an httpOnly cookie, and 302
|
|
21
|
-
* to One's /oauth/authorize (full recipe in the README). Relative
|
|
22
|
-
* paths resolve against the host page's origin.
|
|
23
|
-
*/
|
|
24
|
-
authorize: {
|
|
25
|
-
url: string;
|
|
26
|
-
};
|
|
27
|
-
/** Theme for One's hosted page. Carried on the URL fragment
|
|
28
|
-
* (#one_theme=…), which survives the redirect chain — the consumer's
|
|
29
|
-
* backend forwards nothing. */
|
|
30
|
-
appTheme?: "dark" | "light";
|
|
31
|
-
/** Fired on return when the callback redirect carried
|
|
32
|
-
* ?one_connect=success. The token exchange already happened on the
|
|
33
|
-
* consumer's backend by this point. */
|
|
34
|
-
onSuccess?: () => void;
|
|
35
|
-
/** Fired on return when the callback redirect carried an error. */
|
|
36
|
-
onError?: (error: string) => void;
|
|
37
|
-
/** Reserved for future use — the hosted flow's cancel path returns
|
|
38
|
-
* through onError with the OAuth error string. */
|
|
39
|
-
onClose?: () => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface OneConnectHandle {
|
|
43
|
-
/** Navigates the tab to One's hosted connect flow. */
|
|
44
|
-
open: () => void;
|
|
45
|
-
/** No-op in the full-page flow — kept so hosts can call it
|
|
46
|
-
* unconditionally (e.g. on unmount). */
|
|
47
|
-
close: (options?: { keepResult?: boolean }) => void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** A provider chip on the pre-built button. The SDK ships no One URLs,
|
|
51
|
-
* so icon sources are the consumer's to provide. */
|
|
52
|
-
export interface ConnectButtonPlatform {
|
|
53
|
-
name: string;
|
|
54
|
-
imageUrl?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface ConnectButtonOptions {
|
|
58
|
-
/** Everything useOneConnect takes — the button wires open() and the
|
|
59
|
-
* Connecting/Connected states around your callbacks. */
|
|
60
|
-
connect: OneConnectProps;
|
|
61
|
-
/** "Connect your apps" unless overridden. */
|
|
62
|
-
label?: string;
|
|
63
|
-
/** default = neutral pill; accent = brand-colored pill; block =
|
|
64
|
-
* full-width consent card with description + "Secured by One" foot. */
|
|
65
|
-
variant?: "default" | "accent" | "block";
|
|
66
|
-
/** Matches the host page, not One's page (that's connect.appTheme). */
|
|
67
|
-
theme?: "light" | "dark";
|
|
68
|
-
/** Provider chips fanned on hover; first four render, the rest fold
|
|
69
|
-
* into the +N chip together with moreCount. */
|
|
70
|
-
platforms?: ConnectButtonPlatform[];
|
|
71
|
-
/** Extra count for the +N chip (e.g. 274 for "the whole catalog"). */
|
|
72
|
-
moreCount?: number;
|
|
73
|
-
/** Sub-line on the block variant, shown while idle. */
|
|
74
|
-
description?: string;
|
|
75
|
-
/** accent variant fill; lime (#CCFF00) when omitted. */
|
|
76
|
-
accentColor?: string;
|
|
77
|
-
/** Label for the connected state ("4 apps connected"). */
|
|
78
|
-
connectedLabel?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface ConnectButtonHandle {
|
|
82
|
-
/** Manually override the visual state. */
|
|
83
|
-
setState: (state: "idle" | "connecting" | "connected") => void;
|
|
84
|
-
/** Remove the button and tear down the flow wiring. */
|
|
85
|
-
destroy: () => void;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Registers the <one-connect-button> custom element (no-op on servers
|
|
89
|
-
* and when already registered). Importing the package does this
|
|
90
|
-
* automatically in browsers; calling it manually is only needed if a
|
|
91
|
-
* bundler stripped module side effects. */
|
|
92
|
-
export declare function registerConnectButton(): void;
|
package/src/react-types.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/** Hand-maintained declarations for the "@withone/connect/react" subpath. */
|
|
2
|
-
import type { CSSProperties, ReactElement } from "react";
|
|
3
|
-
import type { ConnectButtonPlatform } from "./types";
|
|
4
|
-
|
|
5
|
-
export interface ConnectButtonProps {
|
|
6
|
-
authorizeUrl: string;
|
|
7
|
-
appTheme?: "light" | "dark";
|
|
8
|
-
onSuccess?: () => void;
|
|
9
|
-
onError?: (error: string) => void;
|
|
10
|
-
onClose?: () => void;
|
|
11
|
-
label?: string;
|
|
12
|
-
variant?: "default" | "accent" | "block";
|
|
13
|
-
theme?: "light" | "dark";
|
|
14
|
-
platforms?: ConnectButtonPlatform[];
|
|
15
|
-
moreCount?: number;
|
|
16
|
-
description?: string;
|
|
17
|
-
accentColor?: string;
|
|
18
|
-
connectedLabel?: string;
|
|
19
|
-
className?: string;
|
|
20
|
-
style?: CSSProperties;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export declare function ConnectButton(props: ConnectButtonProps): ReactElement;
|
package/src/svelte-types.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/** Hand-maintained declarations for the "@withone/connect/svelte" subpath. */
|
|
2
|
-
import type { ConnectButtonPlatform } from "./types";
|
|
3
|
-
|
|
4
|
-
export interface ConnectButtonProps {
|
|
5
|
-
authorizeUrl: string;
|
|
6
|
-
appTheme?: "light" | "dark";
|
|
7
|
-
onSuccess?: () => void;
|
|
8
|
-
onError?: (error: string) => void;
|
|
9
|
-
onClose?: () => void;
|
|
10
|
-
label?: string;
|
|
11
|
-
variant?: "default" | "accent" | "block";
|
|
12
|
-
theme?: "light" | "dark";
|
|
13
|
-
platforms?: ConnectButtonPlatform[];
|
|
14
|
-
moreCount?: number;
|
|
15
|
-
description?: string;
|
|
16
|
-
accentColor?: string;
|
|
17
|
-
connectedLabel?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Svelte action: <div use:connectButton={props} /> */
|
|
21
|
-
export declare function connectButton(
|
|
22
|
-
node: HTMLElement,
|
|
23
|
-
props: ConnectButtonProps,
|
|
24
|
-
): {
|
|
25
|
-
update: (next: ConnectButtonProps) => void;
|
|
26
|
-
destroy: () => void;
|
|
27
|
-
};
|