@withone/connect 0.13.2 → 0.15.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/dist/vue.cjs.js CHANGED
@@ -1 +1 @@
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 a=()=>({...n,onSuccess:()=>o("success"),onError:(e,t)=>o("error",e,t),onCancel:()=>o("cancel")});return e.onMounted(()=>{r.value&&(i=t.renderConnectButton(r.value,a()))}),e.watch(()=>({...n}),()=>{var e;return null===(e=i)||void 0===e?void 0:e.update(a())},{deep:!0}),e.onBeforeUnmount(()=>{var e;null===(e=i)||void 0===e||e.destroy(),i=null}),()=>{var t;return e.h("span",{ref:r,class:"one-connect","data-variant":null!==(t=n.variant)&&void 0!==t?t:"default","data-full-width":n.fullWidth?"":void 0})}}});exports.ConnectButton=o;
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},logos:{type:Array,default:void 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},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 l=()=>({...n,onSuccess:()=>o("success"),onError:(e,t)=>o("error",e,t),onCancel:()=>o("cancel")});return e.onMounted(()=>{r.value&&(i=t.renderConnectButton(r.value,l()))}),e.watch(()=>({...n}),()=>{var e;return null===(e=i)||void 0===e?void 0:e.update(l())},{deep:!0}),e.onBeforeUnmount(()=>{var e;null===(e=i)||void 0===e||e.destroy(),i=null}),()=>{var t;return e.h("span",{ref:r,class:"one-connect","data-variant":null!==(t=n.variant)&&void 0!==t?t:"default","data-full-width":n.fullWidth?"":void 0})}}});exports.ConnectButton=o;
package/dist/vue.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import type { PropType } from "vue";
2
- import { type ConnectButtonPlatformInput, type ConnectButtonSize, type ConnectButtonTheme, type ConnectButtonVariant, type ConnectFailureCode, type OneConnectTheme } from "@withone/connect";
2
+ import { type ConnectButtonLogoInput, 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
+ logos: {
9
+ type: PropType<ConnectButtonLogoInput[]>;
10
+ default: undefined;
11
+ };
12
+ /** @deprecated use logos */
8
13
  platforms: {
9
- type: PropType<ConnectButtonPlatformInput[]>;
14
+ type: PropType<ConnectButtonLogoInput[]>;
10
15
  default: undefined;
11
16
  };
12
17
  connected: {
@@ -37,15 +42,6 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
37
42
  type: PropType<OneConnectTheme>;
38
43
  default: undefined;
39
44
  };
40
- /** @deprecated use connectTheme */
41
- appTheme: {
42
- type: PropType<OneConnectTheme>;
43
- default: undefined;
44
- };
45
- accentColor: {
46
- type: StringConstructor;
47
- default: undefined;
48
- };
49
45
  label: {
50
46
  type: StringConstructor;
51
47
  default: undefined;
@@ -69,8 +65,13 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
69
65
  type: StringConstructor;
70
66
  required: true;
71
67
  };
68
+ logos: {
69
+ type: PropType<ConnectButtonLogoInput[]>;
70
+ default: undefined;
71
+ };
72
+ /** @deprecated use logos */
72
73
  platforms: {
73
- type: PropType<ConnectButtonPlatformInput[]>;
74
+ type: PropType<ConnectButtonLogoInput[]>;
74
75
  default: undefined;
75
76
  };
76
77
  connected: {
@@ -101,15 +102,6 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
101
102
  type: PropType<OneConnectTheme>;
102
103
  default: undefined;
103
104
  };
104
- /** @deprecated use connectTheme */
105
- appTheme: {
106
- type: PropType<OneConnectTheme>;
107
- default: undefined;
108
- };
109
- accentColor: {
110
- type: StringConstructor;
111
- default: undefined;
112
- };
113
105
  label: {
114
106
  type: StringConstructor;
115
107
  default: undefined;
@@ -131,13 +123,12 @@ export declare const ConnectButton: import("vue").DefineComponent<import("vue").
131
123
  label: string;
132
124
  disabled: boolean;
133
125
  connectTheme: OneConnectTheme;
134
- appTheme: OneConnectTheme;
135
- platforms: ConnectButtonPlatformInput[];
126
+ logos: ConnectButtonLogoInput[];
127
+ platforms: ConnectButtonLogoInput[];
136
128
  variant: ConnectButtonVariant;
137
129
  size: ConnectButtonSize;
138
130
  theme: ConnectButtonTheme;
139
131
  description: string;
140
132
  fullWidth: boolean;
141
- accentColor: string;
142
133
  connectedLabel: string;
143
134
  }, {}, {}, {}, 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 r,h as i}from"vue";import{renderConnectButton as l}from"@withone/connect";const a={type:Boolean,default:void 0},d=e({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},platforms:{type:Array,default:void 0},connected:a,disabled:a,variant:{type:String,default:void 0},size:{type:String,default:void 0},fullWidth:a,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:a}){const d=t(null);let u=null;const c=()=>({...e,onSuccess:()=>a("success"),onError:(e,t)=>a("error",e,t),onCancel:()=>a("cancel")});return n(()=>{d.value&&(u=l(d.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}),()=>{var t;return i("span",{ref:d,class:"one-connect","data-variant":null!==(t=e.variant)&&void 0!==t?t:"default","data-full-width":e.fullWidth?"":void 0})}}});export{d as ConnectButton};
1
+ import{defineComponent as e,ref as t,onMounted as n,watch as o,onBeforeUnmount as r,h as l}from"vue";import{renderConnectButton as i}from"@withone/connect";const a={type:Boolean,default:void 0},d=e({name:"OneConnectButton",props:{authorizeUrl:{type:String,required:!0},logos:{type:Array,default:void 0},platforms:{type:Array,default:void 0},connected:a,disabled:a,variant:{type:String,default:void 0},size:{type:String,default:void 0},fullWidth:a,theme:{type:String,default:void 0},connectTheme:{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:a}){const d=t(null);let u=null;const c=()=>({...e,onSuccess:()=>a("success"),onError:(e,t)=>a("error",e,t),onCancel:()=>a("cancel")});return n(()=>{d.value&&(u=i(d.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}),()=>{var t;return l("span",{ref:d,class:"one-connect","data-variant":null!==(t=e.variant)&&void 0!==t?t:"default","data-full-width":e.fullWidth?"":void 0})}}});export{d as ConnectButton};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.13.2",
3
+ "version": "0.15.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",
@@ -110,40 +110,53 @@ import { ConnectButton } from "@withone/connect/react";
110
110
 
111
111
  <ConnectButton
112
112
  authorizeUrl="/api/one/authorize"
113
- platforms={["gmail", "stripe"]} // connector slugs
113
+ logos={["gmail", "stripe"]} // connector slugs, decoration only
114
114
  connected={hasGrant} // from the server: await oneConnect.isConnected(userId)
115
115
  onSuccess={() => { /* refetch app state */ }}
116
116
  onError={(message) => { /* show message */ }}
117
117
  />
118
118
  ```
119
119
 
120
- Optional props: `variant` ("default" | "accent" | "block"), `accentColor`,
121
- `size` ("sm" | "md" | "lg"), `fullWidth`, `theme` ("light" | "dark" | "auto"),
122
- `label`, `description`, `disabled`.
120
+ Optional props: `variant` ("default" | "accent" | "block"), `size` ("sm" |
121
+ "md" | "lg"), `fullWidth`, `theme` ("light" | "dark" | "auto"), `label`,
122
+ `description`, `disabled`. The accent variant's colours come from the host's
123
+ `--one-connect-accent` and `--one-connect-accent-fg` CSS variables.
123
124
 
124
125
  Vue: `@withone/connect/vue`, same props. Svelte: `use:connectButton` from
125
126
  `@withone/connect/svelte`. Anything else: `import "@withone/connect"` and use
126
- `<one-connect-button authorize-url="/api/one/authorize" platforms="gmail, stripe">`.
127
+ `<one-connect-button authorize-url="/api/one/authorize" logos="gmail, stripe">`.
127
128
  A custom button in React: `useOneConnect({ authorizeUrl })` returns `{ open, status }`.
128
129
 
129
130
  ## 6 - Calling One with the grant
130
131
 
132
+ The same four steps the One CLI takes: find the action, read its knowledge,
133
+ run it. Always read the knowledge before running an action for the first
134
+ time; it names the required fields, the encoding and any header.
135
+
131
136
  ```ts
132
- const connections = await oneConnect.listConnections(userId); // [{ key, platform, access }]
133
- const actions = await oneConnect.listActions(userId, "gmail"); // [{ _id, title, method, path }]
137
+ const connections = await oneConnect.listConnections(userId); // [{ key, platform, access }]
138
+ const [action] = await oneConnect.searchActions(userId, "stripe", "create an invoice"); // best first, 5 by default
139
+ const guide = await oneConnect.getActionKnowledge(userId, action._id); // { knowledge (Markdown), ioSchema, method, path, tags }
134
140
 
135
141
  const reply = await oneConnect.runAction(userId, {
136
142
  connectionKey: connection.key,
137
143
  actionId: action._id,
138
- method: action.method,
139
- path: action.path,
140
- body: payload,
144
+ body: payload, // what the guide asks for
145
+ pathParams: { calendarId: "primary" }, // values for {{placeholders}} in the path
146
+ query: { limit: "10" },
147
+ encoding: "json", // or "form" / "multipart", as the guide says
148
+ headers: {}, // only when the guide names one
141
149
  });
142
150
  // { status, ok, blockedByGrant, data }
143
151
  ```
144
152
 
145
- Do not set any header. The package adds the connect key and the user's id
146
- to every call it makes.
153
+ `runAction` takes the method and path from the action, fills the path's
154
+ placeholders, puts the connection key in the body of an action One serves
155
+ itself (tag `custom`), and encodes the body as asked. `listActions(userId,
156
+ platform)` lists a whole catalog when search is not enough.
157
+
158
+ Do not set any auth header. The package adds the connect key and the
159
+ user's id to every call it makes.
147
160
 
148
161
  A `403` reply with `blockedByGrant: true` means the call is outside what
149
162
  the user granted. Do not retry it.
package/src/button.ts CHANGED
@@ -20,8 +20,10 @@ import type {
20
20
  * custom properties set through the CSSOM, which such a policy allows.
21
21
  *
22
22
  * Theming hooks for the host page: `--one-connect-font`,
23
- * `--one-connect-radius`, and `::part(button)` / `::part(label)` on the
24
- * host (`one-connect-button`, or `.one-connect` for the wrappers).
23
+ * `--one-connect-radius`, `--one-connect-accent` and
24
+ * `--one-connect-accent-fg` (the accent variant's fill and text), and
25
+ * `::part(button)` / `::part(label)` on the host (`one-connect-button`,
26
+ * or `.one-connect` for the wrappers).
25
27
  */
26
28
 
27
29
  const MAX_VISIBLE_CHIPS = 3;
@@ -129,57 +131,6 @@ function adoptStyles(root: ShadowRoot): void {
129
131
  root.appendChild(style);
130
132
  }
131
133
 
132
- /* ── Accent contrast ──────────────────────────────────────────────── */
133
-
134
- function parseRgb(color: string): [number, number, number] | null {
135
- const value = color.trim().toLowerCase();
136
- const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/.exec(value);
137
- if (hex) {
138
- const digits = hex[1].length === 3 ? hex[1].replace(/./g, "$&$&") : hex[1];
139
- return [0, 2, 4].map((i) => parseInt(digits.slice(i, i + 2), 16)) as [
140
- number,
141
- number,
142
- number,
143
- ];
144
- }
145
- const rgb = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/.exec(value);
146
- return rgb ? [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])] : null;
147
- }
148
-
149
- /** Any CSS colour to sRGB, through a canvas when it is not already hex
150
- * or rgb() ("navy", "hsl(…)"). Null when the browser cannot say. */
151
- function toRgb(color: string): [number, number, number] | null {
152
- const direct = parseRgb(color);
153
- if (direct) return direct;
154
- try {
155
- const context = document.createElement("canvas").getContext("2d");
156
- if (!context) return null;
157
- context.fillStyle = "#000";
158
- context.fillStyle = color;
159
- return parseRgb(String(context.fillStyle));
160
- } catch {
161
- return null;
162
- }
163
- }
164
-
165
- const luminance = ([r, g, b]: [number, number, number]): number => {
166
- const channel = (c: number) => {
167
- const s = c / 255;
168
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
169
- };
170
- return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
171
- };
172
-
173
- /** Black or white, whichever has the higher WCAG contrast on `color`. */
174
- export function readableTextOn(color: string): string {
175
- const rgb = toRgb(color);
176
- if (!rgb) return CARBON;
177
- const l = luminance(rgb);
178
- const onWhite = 1.05 / (l + 0.05);
179
- const onCarbon = (l + 0.05) / (luminance([10, 12, 11]) + 0.05);
180
- return onWhite > onCarbon ? WHITE : CARBON;
181
- }
182
-
183
134
  /* ── Rendering ────────────────────────────────────────────────────── */
184
135
 
185
136
  const el = <K extends keyof HTMLElementTagNameMap>(
@@ -200,7 +151,7 @@ const icon = (svg: string, className = "icon"): HTMLSpanElement => {
200
151
  };
201
152
 
202
153
  function buildStack(props: ConnectButtonProps): HTMLElement | null {
203
- const platforms = normalizePlatforms(props.platforms);
154
+ const platforms = normalizePlatforms(props.logos ?? props.platforms);
204
155
  if (platforms.length === 0) return null;
205
156
  const stack = el("span", "stack");
206
157
  stack.setAttribute("aria-hidden", "true");
@@ -230,7 +181,7 @@ const flowOptions = (
230
181
  handlers: Pick<OneConnectFlowOptions, "onSuccess" | "onError" | "onCancel">,
231
182
  ): OneConnectFlowOptions => ({
232
183
  authorizeUrl: props.authorizeUrl,
233
- connectTheme: props.connectTheme ?? props.appTheme,
184
+ connectTheme: props.connectTheme,
234
185
  ...handlers,
235
186
  });
236
187
 
@@ -311,22 +262,6 @@ export function renderConnectButton(
311
262
  ? (props.connectedLabel ?? "Connected")
312
263
  : (props.label ?? "Connect your apps");
313
264
 
314
- // Colour props travel as custom properties on the button inside the
315
- // shadow root, set through the CSSOM: allowed under a strict CSP, and
316
- // never written to the host, whose attributes belong to whoever
317
- // rendered it (a server-rendered page must hydrate unchanged).
318
- if (variant === "accent") {
319
- const accent = props.accentColor?.trim() || LIME;
320
- button.style.setProperty("--one-connect-accent", accent);
321
- button.style.setProperty(
322
- "--one-connect-accent-fg",
323
- readableTextOn(accent),
324
- );
325
- } else {
326
- button.style.removeProperty("--one-connect-accent");
327
- button.style.removeProperty("--one-connect-accent-fg");
328
- }
329
-
330
265
  button.dataset.variant = variant;
331
266
  button.dataset.size = props.size ?? "md";
332
267
  button.dataset.theme = props.theme ?? "light";
@@ -438,6 +373,7 @@ export function mountConnectButton(
438
373
 
439
374
  const ATTRIBUTES = [
440
375
  "authorize-url",
376
+ "logos",
441
377
  "platforms",
442
378
  "connected",
443
379
  "disabled",
@@ -446,8 +382,6 @@ const ATTRIBUTES = [
446
382
  "full-width",
447
383
  "theme",
448
384
  "connect-theme",
449
- "app-theme",
450
- "accent-color",
451
385
  "label",
452
386
  "connected-label",
453
387
  "description",
@@ -510,17 +444,16 @@ export function registerConnectButton(): void {
510
444
  private props(authorizeUrl: string): ConnectButtonProps {
511
445
  return {
512
446
  authorizeUrl,
513
- platforms: parsePlatformsAttribute(this.getAttribute("platforms")),
447
+ logos: parsePlatformsAttribute(
448
+ this.getAttribute("logos") ?? this.getAttribute("platforms"),
449
+ ),
514
450
  connected: flag(this, "connected"),
515
451
  disabled: flag(this, "disabled"),
516
452
  variant: attributeAs(this, "variant", ["default", "accent", "block"]),
517
453
  size: attributeAs(this, "size", ["sm", "md", "lg"]),
518
454
  fullWidth: flag(this, "full-width"),
519
455
  theme: attributeAs(this, "theme", ["light", "dark", "auto"]),
520
- connectTheme:
521
- attributeAs(this, "connect-theme", ["light", "dark"]) ??
522
- attributeAs(this, "app-theme", ["light", "dark"]),
523
- accentColor: this.getAttribute("accent-color") ?? undefined,
456
+ connectTheme: attributeAs(this, "connect-theme", ["light", "dark"]),
524
457
  label: this.getAttribute("label") ?? undefined,
525
458
  connectedLabel: this.getAttribute("connected-label") ?? undefined,
526
459
  description: this.getAttribute("description") ?? undefined,
package/src/flow.ts CHANGED
@@ -92,7 +92,7 @@ function bindPageshow(): void {
92
92
  }
93
93
 
94
94
  const authorizeUrlFor = (options: OneConnectFlowOptions): string => {
95
- const theme = options.connectTheme ?? options.appTheme;
95
+ const theme = options.connectTheme;
96
96
  try {
97
97
  const url = new URL(options.authorizeUrl, window.location.origin);
98
98
  if (theme) url.hash = `${THEME_PARAM}=${theme}`;
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export {
6
6
  } from "./button";
7
7
  export type {
8
8
  ConnectButtonHandle,
9
+ ConnectButtonLogo,
10
+ ConnectButtonLogoInput,
9
11
  ConnectButtonPlatform,
10
12
  ConnectButtonPlatformInput,
11
13
  ConnectButtonProps,
package/src/platforms.ts CHANGED
@@ -94,7 +94,7 @@ export function normalizePlatforms(
94
94
  }
95
95
 
96
96
  /**
97
- * The `platforms` attribute on <one-connect-button>: a comma list of
97
+ * The `logos` attribute on <one-connect-button>: a comma list of
98
98
  * slugs ("stripe, notion") or, for overrides, a JSON array of
99
99
  * {slug, name, imageUrl} objects.
100
100
  */
package/src/react.ts CHANGED
@@ -30,15 +30,14 @@ export interface ConnectButtonProps extends ConnectButtonCoreProps {
30
30
  const visualKey = (props: ConnectButtonCoreProps): string =>
31
31
  JSON.stringify([
32
32
  props.authorizeUrl,
33
- props.platforms ?? [],
33
+ props.logos ?? props.platforms ?? [],
34
34
  props.connected,
35
35
  props.disabled,
36
36
  props.variant,
37
37
  props.size,
38
38
  props.fullWidth,
39
39
  props.theme,
40
- props.connectTheme ?? props.appTheme,
41
- props.accentColor,
40
+ props.connectTheme,
42
41
  props.label,
43
42
  props.connectedLabel,
44
43
  props.description,