@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/dist/useOneConnect.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
import type { OneConnectHandle,
|
|
2
|
-
|
|
1
|
+
import type { OneConnectHandle, OneConnectOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Wires the connect flow to any element. A plain function rather than a
|
|
4
|
+
* React hook, so it works from every framework: call it once where the
|
|
5
|
+
* button lives and pass `open` to a click.
|
|
6
|
+
*
|
|
7
|
+
* The flow is a full-page redirect: the tab goes to the app's authorize
|
|
8
|
+
* route, on to One's hosted page, back to the app's callback route, and
|
|
9
|
+
* home. On the way home the callback route appends `?one_connect=…` to
|
|
10
|
+
* the URL it redirects to; this function reads that on load, fires
|
|
11
|
+
* onSuccess or onError once, and removes the params from the address bar.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useOneConnect: (options: OneConnectOptions) => OneConnectHandle;
|
package/dist/vue.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("vue");function t(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 n="one_connect",o="one_connect_message";let i=!1;const r=e=>{let t=!1;if("undefined"!=typeof window&&!i){const l=new URLSearchParams(window.location.search),c=l.get(n);if("success"===c||"error"===c){var r;i=!0;const a=null!==(r=l.get(o))&&void 0!==r?r:void 0,s=()=>{const e=new URLSearchParams(window.location.search);(e.has(n)||e.has(o))&&(e.delete(n),e.delete(o),window.history.replaceState(null,"",window.location.pathname+(e.toString()?`?${e.toString()}`:"")+window.location.hash))};s();for(const e of[50,500,2e3])window.setTimeout(s,e);window.setTimeout(()=>{if(!t){t=!0;try{var n,o;"success"===c?null===(n=e.onSuccess)||void 0===n||n.call(e):null===(o=e.onError)||void 0===o||o.call(e,null!=a?a:"The connection was not completed.")}catch{}}},0)}}return{open:()=>{"undefined"!=typeof window&&window.location.assign((()=>{try{const t=new URL(e.authorize.url,"undefined"==typeof window?void 0:window.location.origin);return e.appTheme&&(t.hash=`one_theme=${e.appTheme}`),t.toString()}catch{return e.authorize.url}})())},close:e=>{}}},l="one-connect-button-styles",c="cubic-bezier(.2,.9,.25,1)",a={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)"},d="#CCFF00",p="#3FE3A5",u="#0A0C0B";function h(e){const t=e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return t?`https://assets.withone.ai/connectors/${t}.svg`:""}const m=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(l))return;const e=document.createElement("style");e.id=l,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 ${c},box-shadow .2s ${c},opacity .2s ${c}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${p};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 ${c};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 ${c},opacity .2s ${c}}`,".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 i="dark"===t.theme?s:a,m=null!==(n=t.variant)&&void 0!==n?n:"default",f=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let b="idle",v=!1;const w=r({...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)}}),g=document.createElement("button");g.type="button",g.className="block"===m?"owcb owcb-block":"owcb",g.addEventListener("click",()=>{"idle"!==b&&"connected"!==b||(y("connecting"),w.open())});const x=()=>{var e,n,o;const r="accent"===m,l="block"===m,c=l?i.surface:r?null!==(e=t.accentColor)&&void 0!==e?e:d:i.btnBg,a=l?i.fg:r?u:i.btnFg,s=l?i.surface:c,v=u;g.style.background="connected"!==b||l?c:p,g.style.color="connected"!==b||l?a:u,g.style.boxShadow=l?`inset 0 0 0 1px ${i.line}`:"connected"===b?"none":r?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:d}38`:i.shadow,g.disabled="connecting"===b,g.innerHTML="";const w=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const l=document.createElement("span");l.className="owcb-stack",l.setAttribute("aria-hidden","true");for(const e of r.slice(0,3)){var c;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=u},i=null!==(c=e.imageUrl)&&void 0!==c?c:h(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 a=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-3);if(a>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=`+${a}`,l.appendChild(e)}return l}(t,s,v);if(l){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 w&&e.appendChild(w);if(g.appendChild(e),t.description&&"idle"===b){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,g.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.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:${i.fg}">one</span>`,void g.appendChild(o)}if("connecting"===b){const e=document.createElement("span");e.className="owcb-spinner",g.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",g.appendChild(e)}else w&&g.appendChild(w);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,g.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",g.appendChild(e)}},y=e=>{v||(b=e,x())};return x(),e.appendChild(g),{setState:y,destroy:()=>{v=!0,w.close({keepResult:!0}),g.remove()}}};!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),t(this,"onSuccess",null),t(this,"onError",null),t(this,"onClose",null),t(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,r,l,c;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const a=this.getAttribute("authorize-url");if(!a)return;let s;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(s=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=m(this,{connect:{authorize:{url:a},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:s,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(l=this.getAttribute("accent-color"))&&void 0!==l?l:void 0,connectedLabel:null!==(c=this.getAttribute("connected-label"))&&void 0!==c?c:void 0})}}t(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}();const f=e.defineComponent({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},appTheme:{type:String,default:void 0},label:{type:String,default:void 0},variant:{type:String,default:void 0},theme:{type:String,default:void 0},platforms:{type:Array,default:void 0},moreCount:{type:Number,default:void 0},description:{type:String,default:void 0},accentColor:{type:String,default:void 0},connectedLabel:{type:String,default:void 0}},emits:["success","error","close"],setup(t,{emit:n}){const o=e.ref(null);let i=null;const r=()=>{var e;null===(e=i)||void 0===e||e.destroy(),i=null,o.value&&(i=m(o.value,function(e,t){const n=null!=t?t:e;return{connect:{authorize:{url:e.authorizeUrl},appTheme:e.appTheme,onSuccess:()=>{var e;return null===(e=n.onSuccess)||void 0===e?void 0:e.call(n)},onError:e=>{var t;return null===(t=n.onError)||void 0===t?void 0:t.call(n,e)},onClose:()=>{var e;return null===(e=n.onClose)||void 0===e?void 0:e.call(n)}},label:e.label,variant:e.variant,theme:e.theme,platforms:e.platforms,moreCount:e.moreCount,description:e.description,accentColor:e.accentColor,connectedLabel:e.connectedLabel}}({...t},{onSuccess:()=>n("success"),onError:e=>n("error",e),onClose:()=>n("close")})))};return e.onMounted(r),e.watch(()=>{var e;return[t.authorizeUrl,t.appTheme,t.label,t.variant,t.theme,JSON.stringify(null!==(e=t.platforms)&&void 0!==e?e:[]),t.moreCount,t.description,t.accentColor,t.connectedLabel]},r),e.onBeforeUnmount(()=>{var e;null===(e=i)||void 0===e||e.destroy(),i=null}),()=>e.h("div",{ref:o})}});exports.ConnectButton=f;
|
|
1
|
+
"use strict";var e=require("vue"),t=require("@withone/connect");const o=e.defineComponent({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},appTheme:{type:String,default:void 0},label:{type:String,default:void 0},variant:{type:String,default:void 0},theme:{type:String,default:void 0},platforms:{type:Array,default:void 0},moreCount:{type:Number,default:void 0},description:{type:String,default:void 0},accentColor:{type:String,default:void 0},connectedLabel:{type:String,default:void 0}},emits:{success:()=>!0,error:e=>"string"==typeof e},setup(o,{emit:n}){const r=e.ref(null);let u=null;const i=()=>{var e;null===(e=u)||void 0===e||e.destroy(),u=null,r.value&&(u=t.mountConnectButton(r.value,t.optionsFromProps({...o},{onSuccess:()=>n("success"),onError:e=>n("error",e)})))};return e.onMounted(i),e.watch(()=>t.propsIdentity({...o}),i),e.onBeforeUnmount(()=>{var e;null===(e=u)||void 0===e||e.destroy(),u=null}),()=>e.h("div",{ref:r})}});exports.ConnectButton=o;
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { PropType } from "vue";
|
|
2
|
-
import type
|
|
2
|
+
import { type ConnectButtonPlatformInput, type ConnectButtonVariant, 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
8
|
appTheme: {
|
|
9
|
-
type: PropType<
|
|
9
|
+
type: PropType<OneConnectTheme>;
|
|
10
10
|
default: undefined;
|
|
11
11
|
};
|
|
12
12
|
label: {
|
|
@@ -14,15 +14,15 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
14
14
|
default: undefined;
|
|
15
15
|
};
|
|
16
16
|
variant: {
|
|
17
|
-
type: PropType<
|
|
17
|
+
type: PropType<ConnectButtonVariant>;
|
|
18
18
|
default: undefined;
|
|
19
19
|
};
|
|
20
20
|
theme: {
|
|
21
|
-
type: PropType<
|
|
21
|
+
type: PropType<OneConnectTheme>;
|
|
22
22
|
default: undefined;
|
|
23
23
|
};
|
|
24
24
|
platforms: {
|
|
25
|
-
type: PropType<
|
|
25
|
+
type: PropType<ConnectButtonPlatformInput[]>;
|
|
26
26
|
default: undefined;
|
|
27
27
|
};
|
|
28
28
|
moreCount: {
|
|
@@ -43,13 +43,16 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
43
43
|
};
|
|
44
44
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
45
45
|
[key: string]: any;
|
|
46
|
-
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin,
|
|
46
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
47
|
+
success: () => true;
|
|
48
|
+
error: (message: string) => boolean;
|
|
49
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
47
50
|
authorizeUrl: {
|
|
48
51
|
type: StringConstructor;
|
|
49
52
|
required: true;
|
|
50
53
|
};
|
|
51
54
|
appTheme: {
|
|
52
|
-
type: PropType<
|
|
55
|
+
type: PropType<OneConnectTheme>;
|
|
53
56
|
default: undefined;
|
|
54
57
|
};
|
|
55
58
|
label: {
|
|
@@ -57,15 +60,15 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
57
60
|
default: undefined;
|
|
58
61
|
};
|
|
59
62
|
variant: {
|
|
60
|
-
type: PropType<
|
|
63
|
+
type: PropType<ConnectButtonVariant>;
|
|
61
64
|
default: undefined;
|
|
62
65
|
};
|
|
63
66
|
theme: {
|
|
64
|
-
type: PropType<
|
|
67
|
+
type: PropType<OneConnectTheme>;
|
|
65
68
|
default: undefined;
|
|
66
69
|
};
|
|
67
70
|
platforms: {
|
|
68
|
-
type: PropType<
|
|
71
|
+
type: PropType<ConnectButtonPlatformInput[]>;
|
|
69
72
|
default: undefined;
|
|
70
73
|
};
|
|
71
74
|
moreCount: {
|
|
@@ -85,17 +88,16 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
|
|
|
85
88
|
default: undefined;
|
|
86
89
|
};
|
|
87
90
|
}>> & Readonly<{
|
|
88
|
-
onSuccess?: ((
|
|
89
|
-
onError?: ((
|
|
90
|
-
onClose?: ((...args: any[]) => any) | undefined;
|
|
91
|
+
onSuccess?: (() => any) | undefined;
|
|
92
|
+
onError?: ((message: string) => any) | undefined;
|
|
91
93
|
}>, {
|
|
92
|
-
appTheme: "light" | "dark";
|
|
93
94
|
label: string;
|
|
94
|
-
variant:
|
|
95
|
-
theme:
|
|
96
|
-
platforms:
|
|
97
|
-
moreCount: number;
|
|
95
|
+
variant: ConnectButtonVariant;
|
|
96
|
+
theme: OneConnectTheme;
|
|
97
|
+
platforms: ConnectButtonPlatformInput[];
|
|
98
98
|
description: string;
|
|
99
|
+
appTheme: OneConnectTheme;
|
|
100
|
+
moreCount: number;
|
|
99
101
|
accentColor: string;
|
|
100
102
|
connectedLabel: string;
|
|
101
103
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/vue.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineComponent as e,ref as t,onMounted as n,watch as o,onBeforeUnmount as i,h as r}from"vue";function l(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",a="one_connect_message";let s=!1;const d=e=>{let t=!1;if("undefined"!=typeof window&&!s){const o=new URLSearchParams(window.location.search),i=o.get(c);if("success"===i||"error"===i){var n;s=!0;const r=null!==(n=o.get(a))&&void 0!==n?n:void 0,l=()=>{const e=new URLSearchParams(window.location.search);(e.has(c)||e.has(a))&&(e.delete(c),e.delete(a),window.history.replaceState(null,"",window.location.pathname+(e.toString()?`?${e.toString()}`:"")+window.location.hash))};l();for(const e of[50,500,2e3])window.setTimeout(l,e);window.setTimeout(()=>{if(!t){t=!0;try{var n,o;"success"===i?null===(n=e.onSuccess)||void 0===n||n.call(e):null===(o=e.onError)||void 0===o||o.call(e,null!=r?r:"The connection was not completed.")}catch{}}},0)}}return{open:()=>{"undefined"!=typeof window&&window.location.assign((()=>{try{const t=new URL(e.authorize.url,"undefined"==typeof window?void 0:window.location.origin);return e.appTheme&&(t.hash=`one_theme=${e.appTheme}`),t.toString()}catch{return e.authorize.url}})())},close:e=>{}}},p="one-connect-button-styles",u="cubic-bezier(.2,.9,.25,1)",h={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)"},m={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)"},f="#CCFF00",b="#3FE3A5",v="#0A0C0B";function w(e){const t=e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return t?`https://assets.withone.ai/connectors/${t}.svg`:""}const g=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(p))return;const e=document.createElement("style");e.id=p,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 ${u},box-shadow .2s ${u},opacity .2s ${u}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${b};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 ${u};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 ${u},opacity .2s ${u}}`,".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 i="dark"===t.theme?m:h,r=null!==(n=t.variant)&&void 0!==n?n:"default",l=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let c="idle",a=!1;const s=d({...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)}}),g=document.createElement("button");g.type="button",g.className="block"===r?"owcb owcb-block":"owcb",g.addEventListener("click",()=>{"idle"!==c&&"connected"!==c||(y("connecting"),s.open())});const x=()=>{var e,n,o;const a="accent"===r,s="block"===r,d=s?i.surface:a?null!==(e=t.accentColor)&&void 0!==e?e:f:i.btnBg,p=s?i.fg:a?v:i.btnFg,u=s?i.surface:d,h=v;g.style.background="connected"!==c||s?d:b,g.style.color="connected"!==c||s?p:v,g.style.boxShadow=s?`inset 0 0 0 1px ${i.line}`:"connected"===c?"none":a?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:f}38`:i.shadow,g.disabled="connecting"===c,g.innerHTML="";const m=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const l=document.createElement("span");l.className="owcb-stack",l.setAttribute("aria-hidden","true");for(const e of r.slice(0,3)){var c;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=v},i=null!==(c=e.imageUrl)&&void 0!==c?c:w(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 a=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-3);if(a>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=`+${a}`,l.appendChild(e)}return l}(t,u,h);if(s){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"===c?"Connecting…":"connected"===c?null!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":l,e.appendChild(n),"connecting"===c){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else m&&e.appendChild(m);if(g.appendChild(e),t.description&&"idle"===c){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,g.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.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:${i.fg}">one</span>`,void g.appendChild(o)}if("connecting"===c){const e=document.createElement("span");e.className="owcb-spinner",g.appendChild(e)}else if("connected"===c){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",g.appendChild(e)}else m&&g.appendChild(m);const y=document.createElement("span");if(y.className="owcb-label",y.textContent="connecting"===c?"Connecting…":"connected"===c?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":l,g.appendChild(y),"idle"===c){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",g.appendChild(e)}},y=e=>{a||(c=e,x())};return x(),e.appendChild(g),{setState:y,destroy:()=>{a=!0,s.close({keepResult:!0}),g.remove()}}};!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),l(this,"onSuccess",null),l(this,"onError",null),l(this,"onClose",null),l(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,r,l,c;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const a=this.getAttribute("authorize-url");if(!a)return;let s;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(s=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=g(this,{connect:{authorize:{url:a},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:s,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(l=this.getAttribute("accent-color"))&&void 0!==l?l:void 0,connectedLabel:null!==(c=this.getAttribute("connected-label"))&&void 0!==c?c:void 0})}}l(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}();const x=e({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},appTheme:{type:String,default:void 0},label:{type:String,default:void 0},variant:{type:String,default:void 0},theme:{type:String,default:void 0},platforms:{type:Array,default:void 0},moreCount:{type:Number,default:void 0},description:{type:String,default:void 0},accentColor:{type:String,default:void 0},connectedLabel:{type:String,default:void 0}},emits:["success","error","close"],setup(e,{emit:l}){const c=t(null);let a=null;const s=()=>{var t;null===(t=a)||void 0===t||t.destroy(),a=null,c.value&&(a=g(c.value,function(e,t){const n=null!=t?t:e;return{connect:{authorize:{url:e.authorizeUrl},appTheme:e.appTheme,onSuccess:()=>{var e;return null===(e=n.onSuccess)||void 0===e?void 0:e.call(n)},onError:e=>{var t;return null===(t=n.onError)||void 0===t?void 0:t.call(n,e)},onClose:()=>{var e;return null===(e=n.onClose)||void 0===e?void 0:e.call(n)}},label:e.label,variant:e.variant,theme:e.theme,platforms:e.platforms,moreCount:e.moreCount,description:e.description,accentColor:e.accentColor,connectedLabel:e.connectedLabel}}({...e},{onSuccess:()=>l("success"),onError:e=>l("error",e),onClose:()=>l("close")})))};return n(s),o(()=>{var t;return[e.authorizeUrl,e.appTheme,e.label,e.variant,e.theme,JSON.stringify(null!==(t=e.platforms)&&void 0!==t?t:[]),e.moreCount,e.description,e.accentColor,e.connectedLabel]},s),i(()=>{var e;null===(e=a)||void 0===e||e.destroy(),a=null}),()=>r("div",{ref:c})}});export{x as ConnectButton};
|
|
1
|
+
import{defineComponent as e,ref as t,onMounted as r,watch as o,onBeforeUnmount as n,h as i}from"vue";import{propsIdentity as l,mountConnectButton as u,optionsFromProps as d}from"@withone/connect";const a=e({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},appTheme:{type:String,default:void 0},label:{type:String,default:void 0},variant:{type:String,default:void 0},theme:{type:String,default:void 0},platforms:{type:Array,default:void 0},moreCount:{type:Number,default:void 0},description:{type:String,default:void 0},accentColor:{type:String,default:void 0},connectedLabel:{type:String,default:void 0}},emits:{success:()=>!0,error:e=>"string"==typeof e},setup(e,{emit:a}){const p=t(null);let s=null;const v=()=>{var t;null===(t=s)||void 0===t||t.destroy(),s=null,p.value&&(s=u(p.value,d({...e},{onSuccess:()=>a("success"),onError:e=>a("error",e)})))};return r(v),o(()=>l({...e}),v),n(()=>{var e;null===(e=s)||void 0===e||e.destroy(),s=null}),()=>i("div",{ref:p})}});export{a as ConnectButton};
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import type { ConnectButtonOptions,
|
|
2
|
-
/** The flat prop shape every framework wrapper exposes
|
|
3
|
-
* Vue props, Svelte action options
|
|
4
|
-
*
|
|
5
|
-
export interface
|
|
1
|
+
import type { ConnectButtonOptions, ConnectButtonPlatformInput, ConnectButtonVariant, OneConnectTheme } from "./types";
|
|
2
|
+
/** The flat prop shape every framework wrapper exposes: React props,
|
|
3
|
+
* Vue props, the Svelte action's options. One place turns it into the
|
|
4
|
+
* core mountConnectButton options. */
|
|
5
|
+
export interface ConnectButtonProps {
|
|
6
|
+
/** The app's own backend authorize route; relative is fine. */
|
|
6
7
|
authorizeUrl: string;
|
|
7
|
-
|
|
8
|
+
/** Theme of One's hosted page. */
|
|
9
|
+
appTheme?: OneConnectTheme;
|
|
8
10
|
onSuccess?: () => void;
|
|
9
|
-
onError?: (
|
|
10
|
-
onClose?: () => void;
|
|
11
|
+
onError?: (message: string) => void;
|
|
11
12
|
label?: string;
|
|
12
|
-
variant?:
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
variant?: ConnectButtonVariant;
|
|
14
|
+
/** Matches the host page. */
|
|
15
|
+
theme?: OneConnectTheme;
|
|
16
|
+
/** Connector slugs, or objects to override name or logo. */
|
|
17
|
+
platforms?: ConnectButtonPlatformInput[];
|
|
15
18
|
moreCount?: number;
|
|
16
19
|
description?: string;
|
|
17
20
|
accentColor?: string;
|
|
18
21
|
connectedLabel?: string;
|
|
19
22
|
}
|
|
20
|
-
export
|
|
23
|
+
export interface ConnectButtonCallbacks {
|
|
21
24
|
onSuccess?: () => void;
|
|
22
|
-
onError?: (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
onError?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function optionsFromProps(props: ConnectButtonProps, callbacks?: ConnectButtonCallbacks): ConnectButtonOptions;
|
|
28
|
+
/** The inputs whose change should remount the button. */
|
|
29
|
+
export declare function propsIdentity(props: ConnectButtonProps): string;
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withone/connect",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.0",
|
|
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",
|
|
7
7
|
"src",
|
|
8
|
+
"skills",
|
|
8
9
|
"README.md",
|
|
9
10
|
"LICENSE"
|
|
10
11
|
],
|
|
11
|
-
"main": "dist/index.
|
|
12
|
+
"main": "dist/index.cjs.js",
|
|
12
13
|
"module": "dist/index.esm.js",
|
|
13
14
|
"types": "dist/index.d.ts",
|
|
14
15
|
"devDependencies": {
|
|
@@ -24,17 +25,15 @@
|
|
|
24
25
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
25
26
|
"@rollup/plugin-terser": "^0.4.4",
|
|
26
27
|
"@tsconfig/recommended": "^1.0.3",
|
|
28
|
+
"@types/node": "^22.15.0",
|
|
27
29
|
"@types/react": "^19.2.18",
|
|
28
|
-
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
29
|
-
"@typescript-eslint/parser": "^7.0.0",
|
|
30
|
-
"eslint": "^8.57.0",
|
|
31
30
|
"prettier": "^3.2.5",
|
|
32
31
|
"react": "^19.2.8",
|
|
33
32
|
"rollup": "^4.12.0",
|
|
34
|
-
"rollup-plugin-copy": "^3.5.0",
|
|
35
33
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
36
34
|
"tslib": "^2.6.2",
|
|
37
35
|
"typescript": "^5.3.3",
|
|
36
|
+
"vitest": "^3.2.4",
|
|
38
37
|
"vue": "^3.5.42"
|
|
39
38
|
},
|
|
40
39
|
"author": "@withoneai",
|
|
@@ -42,11 +41,15 @@
|
|
|
42
41
|
"access": "public"
|
|
43
42
|
},
|
|
44
43
|
"scripts": {
|
|
45
|
-
"
|
|
46
|
-
"build": "rollup -c && npm run verify",
|
|
47
|
-
"verify": "node -
|
|
48
|
-
"
|
|
49
|
-
"
|
|
44
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
45
|
+
"build": "npm run clean && rollup -c && npm run declarations && npm run verify",
|
|
46
|
+
"verify": "node scripts/verify-build.js",
|
|
47
|
+
"typecheck": "tsc --noEmit -p tsconfig.tests.json",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"check": "npm run typecheck && npm run test && npm run build",
|
|
50
|
+
"prepublishOnly": "npm run check",
|
|
51
|
+
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
52
|
+
"declarations": "tsc -p tsconfig.build.json"
|
|
50
53
|
},
|
|
51
54
|
"repository": {
|
|
52
55
|
"type": "git",
|
|
@@ -67,7 +70,7 @@
|
|
|
67
70
|
".": {
|
|
68
71
|
"types": "./dist/index.d.ts",
|
|
69
72
|
"import": "./dist/index.esm.js",
|
|
70
|
-
"require": "./dist/index.
|
|
73
|
+
"require": "./dist/index.cjs.js"
|
|
71
74
|
},
|
|
72
75
|
"./react": {
|
|
73
76
|
"types": "./dist/react.d.ts",
|
|
@@ -83,7 +86,23 @@
|
|
|
83
86
|
"types": "./dist/svelte.d.ts",
|
|
84
87
|
"import": "./dist/svelte.esm.js",
|
|
85
88
|
"require": "./dist/svelte.cjs.js"
|
|
86
|
-
}
|
|
89
|
+
},
|
|
90
|
+
"./server": {
|
|
91
|
+
"types": "./dist/server/index.d.ts",
|
|
92
|
+
"import": "./dist/server/index.esm.js",
|
|
93
|
+
"require": "./dist/server/index.cjs.js"
|
|
94
|
+
},
|
|
95
|
+
"./next": {
|
|
96
|
+
"types": "./dist/next.d.ts",
|
|
97
|
+
"import": "./dist/next.esm.js",
|
|
98
|
+
"require": "./dist/next.cjs.js"
|
|
99
|
+
},
|
|
100
|
+
"./node": {
|
|
101
|
+
"types": "./dist/node.d.ts",
|
|
102
|
+
"import": "./dist/node.esm.js",
|
|
103
|
+
"require": "./dist/node.cjs.js"
|
|
104
|
+
},
|
|
105
|
+
"./package.json": "./package.json"
|
|
87
106
|
},
|
|
88
107
|
"peerDependencies": {
|
|
89
108
|
"react": ">=17",
|
|
@@ -96,5 +115,8 @@
|
|
|
96
115
|
"vue": {
|
|
97
116
|
"optional": true
|
|
98
117
|
}
|
|
118
|
+
},
|
|
119
|
+
"engines": {
|
|
120
|
+
"node": ">=18"
|
|
99
121
|
}
|
|
100
|
-
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: one-connect
|
|
3
|
+
description: Add One Connect to an application so its users can grant the app scoped, revocable access to their own One-connected tools (Gmail, Slack, Notion, Stripe and 500 more). Use when wiring @withone/connect into an app - the button, the two backend routes, and calling One with the grant.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# One Connect
|
|
7
|
+
|
|
8
|
+
You are adding One Connect to this application. Its users will grant the app
|
|
9
|
+
scoped, revocable access to their own One-connected tools. The package does
|
|
10
|
+
the OAuth work; you wire three things: a button, the two routes, and the
|
|
11
|
+
calls you make with the grant.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Browser Your backend One
|
|
15
|
+
<ConnectButton> ------> GET /api/one/authorize ---302---> One's hosted page (connect.withone.ai)
|
|
16
|
+
sign-in code, pick tools, set access
|
|
17
|
+
GET /api/one/callback <--302---- ?code=...&state=...
|
|
18
|
+
exchanges the code, stores the tokens
|
|
19
|
+
302 -> /?one_connect=success
|
|
20
|
+
<------ SDK reads the param and fires onSuccess
|
|
21
|
+
Later: oneConnect.runAction(userId, ...) -> /v1/passthrough, grant enforced by One
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Secrets and tokens never reach the browser.
|
|
25
|
+
|
|
26
|
+
## 1 - Collect from the human first
|
|
27
|
+
|
|
28
|
+
The human creates the app in the One dashboard: Developers -> Connect ->
|
|
29
|
+
New app. Confidential client. The secret is shown once.
|
|
30
|
+
|
|
31
|
+
| Value | Notes |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `ONE_CLIENT_ID` | 40 hex characters |
|
|
34
|
+
| `ONE_CLIENT_SECRET` | starts with `one_secret_` |
|
|
35
|
+
| Registered redirect URI | Must be the exact URL of the callback route (scheme, host, path, no trailing slash). One compares the string as-is and answers `invalid redirect_uri` on any difference. Use `https` for anything public; `http://localhost:3000/api/one/callback` is fine locally. |
|
|
36
|
+
| `ONE_PERMISSION_SET` (optional) | The id of the app's ask: the connectors and levels the consent page opens on. Users can narrow it, never widen it. Without one, the page lists everything the user has connected. |
|
|
37
|
+
| Environment | Production is the default. For the development dashboard set `ONE_API_URL=https://development-api.withone.ai`. |
|
|
38
|
+
|
|
39
|
+
Two facts to tell the human:
|
|
40
|
+
|
|
41
|
+
- The app can show its users one line saying why it asks. It is set on the
|
|
42
|
+
app in the dashboard, not sent by this package.
|
|
43
|
+
- Users can change or revoke the grant from their One dashboard at any time.
|
|
44
|
+
Treat a `401` from One as "ask the user to reconnect", never as a bug.
|
|
45
|
+
|
|
46
|
+
## 2 - Environment (server only)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ONE_CLIENT_ID=...
|
|
50
|
+
ONE_CLIENT_SECRET=one_secret_...
|
|
51
|
+
ONE_REDIRECT_URI=https://yourapp.com/api/one/callback # exactly the registered value
|
|
52
|
+
ONE_PERMISSION_SET=... # optional
|
|
53
|
+
ONE_API_URL=https://api.withone.ai # optional; development-api.withone.ai for development
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 3 - Install and create the client
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install @withone/connect
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// lib/one.ts (server only)
|
|
64
|
+
import { createOneConnect } from "@withone/connect/server";
|
|
65
|
+
|
|
66
|
+
export const oneConnect = createOneConnect({
|
|
67
|
+
clientId: process.env.ONE_CLIENT_ID!,
|
|
68
|
+
clientSecret: process.env.ONE_CLIENT_SECRET!,
|
|
69
|
+
redirectUri: process.env.ONE_REDIRECT_URI!,
|
|
70
|
+
permissionSet: process.env.ONE_PERMISSION_SET,
|
|
71
|
+
oneApiUrl: process.env.ONE_API_URL,
|
|
72
|
+
tokenStore: {
|
|
73
|
+
saveTokens: (userId, tokens) => /* write to this app's database, encrypted, keyed by its user */,
|
|
74
|
+
loadTokens: (userId) => /* read; null when the user never connected */,
|
|
75
|
+
clearTokens: (userId) => /* delete */,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`tokens` is `{ accessToken, refreshToken, expiresAt }`. Use the app's own user
|
|
81
|
+
id as the key. Implement the store on whatever the app already uses; do not
|
|
82
|
+
add a database for it.
|
|
83
|
+
|
|
84
|
+
## 4 - The two routes
|
|
85
|
+
|
|
86
|
+
Next.js App Router (also Remix, SvelteKit, Hono, Bun: anything with the web `Request`):
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// app/api/one/[action]/route.ts
|
|
90
|
+
import { createOneConnectRoutes } from "@withone/connect/next";
|
|
91
|
+
import { oneConnect } from "@/lib/one";
|
|
92
|
+
|
|
93
|
+
export const { GET } = createOneConnectRoutes(oneConnect, {
|
|
94
|
+
identifyUser: async (request) => /* this app's signed-in user id, or null */,
|
|
95
|
+
loginHintFor: async (request) => /* their email, to pre-fill One's sign-in; or null */,
|
|
96
|
+
signInUrl: "/login", // where to send a visitor who is not signed in
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That file serves `/api/one/authorize` and `/api/one/callback`.
|
|
101
|
+
|
|
102
|
+
Express, Fastify, Koa, plain Node:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { createOneConnectHandlers } from "@withone/connect/node";
|
|
106
|
+
import { oneConnect } from "./one";
|
|
107
|
+
|
|
108
|
+
const { authorize, callback } = createOneConnectHandlers(oneConnect, {
|
|
109
|
+
identifyUser: (request) => /* user id or null */,
|
|
110
|
+
});
|
|
111
|
+
app.get("/api/one/authorize", authorize);
|
|
112
|
+
app.get("/api/one/callback", callback);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The routes mint `state` and PKCE, keep them in a per-flow httpOnly cookie
|
|
116
|
+
(`one_tx_<state>`, SameSite=Lax, 30 minutes), verify the state on return,
|
|
117
|
+
exchange the code with the secret over HTTP Basic, store both tokens, and
|
|
118
|
+
redirect to `/` with `?one_connect=success` or
|
|
119
|
+
`?one_connect=error&one_connect_message=...`. Pass `returnTo` to
|
|
120
|
+
`createOneConnect` to land somewhere else.
|
|
121
|
+
|
|
122
|
+
## 5 - The button
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { ConnectButton } from "@withone/connect/react";
|
|
126
|
+
|
|
127
|
+
<ConnectButton
|
|
128
|
+
authorizeUrl="/api/one/authorize"
|
|
129
|
+
platforms={["stripe", "google-calendar"]} // connector slugs; logos and names come from One
|
|
130
|
+
onSuccess={() => { /* refresh app state; the tokens are already stored */ }}
|
|
131
|
+
onError={(message) => { /* show it; the user may simply have declined */ }}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Vue: `import { ConnectButton } from "@withone/connect/vue"` with `authorize-url`,
|
|
136
|
+
`:platforms`, `@success`, `@error`. Svelte: `import { connectButton } from
|
|
137
|
+
"@withone/connect/svelte"` as `use:connectButton={{ authorizeUrl, platforms,
|
|
138
|
+
onSuccess }}`. Anything else: `import "@withone/connect"` registers
|
|
139
|
+
`<one-connect-button authorize-url="/api/one/authorize" platforms="stripe, notion">`,
|
|
140
|
+
which dispatches `success` and `error` events. A custom element:
|
|
141
|
+
`useOneConnect({ authorizeUrl, onSuccess, onError }).open` on a click.
|
|
142
|
+
|
|
143
|
+
The flow is a same-tab redirect. `onSuccess` and `onError` fire once when the
|
|
144
|
+
tab comes back, and the SDK removes the `one_connect` params from the URL.
|
|
145
|
+
|
|
146
|
+
## 6 - Calling One with the grant
|
|
147
|
+
|
|
148
|
+
Everything runs on the server through the client. Tokens refresh themselves.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const connections = await oneConnect.listConnections(userId);
|
|
152
|
+
// [{ key, platform, name, title, image, access }]
|
|
153
|
+
// access.policy: "full" | "methods" (+ methods: ["GET", ...]) | "actions" (+ actions: [{ actionId, title, method }])
|
|
154
|
+
|
|
155
|
+
const actions = await oneConnect.listActions(userId, "gmail");
|
|
156
|
+
// [{ _id, title, method, path }] - what exists, not what is permitted
|
|
157
|
+
|
|
158
|
+
const reply = await oneConnect.runAction(userId, {
|
|
159
|
+
connectionKey: connection.key,
|
|
160
|
+
actionId: action._id,
|
|
161
|
+
method: action.method,
|
|
162
|
+
path: action.path,
|
|
163
|
+
body: payload,
|
|
164
|
+
});
|
|
165
|
+
// { status, ok, blockedByGrant, data }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`blockedByGrant` true means One refused the call because it is outside the
|
|
169
|
+
grant; the provider was never called. Do not retry. Any other `/v1` call:
|
|
170
|
+
`oneConnect.fetch(userId, "/connections", init)`.
|
|
171
|
+
|
|
172
|
+
`OneConnectError` codes: `not_connected` (no tokens stored), `refresh_failed`
|
|
173
|
+
(One refused the refresh; the tokens were cleared; ask the user to connect
|
|
174
|
+
again), `request_failed` (One answered with an error; `status` carries it).
|
|
175
|
+
|
|
176
|
+
## 7 - Rules
|
|
177
|
+
|
|
178
|
+
- Never put the secret in a client bundle, a log line or an error report.
|
|
179
|
+
- Store tokens encrypted, keyed by the app's user. Delete them when the user
|
|
180
|
+
is deleted. The client deletes them itself when a refresh fails.
|
|
181
|
+
- The registered redirect URI and `ONE_REDIRECT_URI` must be the same string.
|
|
182
|
+
- Do not build a completion page. The callback redirect is the completion.
|
|
183
|
+
- Do not write the OAuth steps by hand when the package exposes them.
|
|
184
|
+
|
|
185
|
+
## 8 - Done when all of these pass
|
|
186
|
+
|
|
187
|
+
1. Button -> One's hosted page -> sign-in code from a real inbox -> choose
|
|
188
|
+
tools and levels -> "You're all set" -> back in the app with `onSuccess`
|
|
189
|
+
fired. Repeat once in a private window.
|
|
190
|
+
2. `listConnections` returns only the granted connections, each with `access`.
|
|
191
|
+
3. `runAction` on an action inside the grant reaches the provider; one outside
|
|
192
|
+
it returns `blockedByGrant: true`.
|
|
193
|
+
4. Revoking the app from the user's One dashboard makes the next call throw
|
|
194
|
+
`refresh_failed` or return `401`, and the app shows its reconnect prompt.
|