@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.
Files changed (55) hide show
  1. package/README.md +158 -370
  2. package/dist/button.d.ts +9 -10
  3. package/dist/constants.d.ts +8 -4
  4. package/dist/index.cjs.js +1 -0
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.esm.js +1 -1
  7. package/dist/next.cjs.js +104 -0
  8. package/dist/next.d.ts +36 -0
  9. package/dist/next.esm.js +98 -0
  10. package/dist/node.cjs.js +69 -0
  11. package/dist/node.d.ts +33 -0
  12. package/dist/node.esm.js +67 -0
  13. package/dist/platforms.d.ts +21 -0
  14. package/dist/react.cjs.js +1 -1
  15. package/dist/react.d.ts +2 -19
  16. package/dist/react.esm.js +1 -1
  17. package/dist/return.d.ts +11 -0
  18. package/dist/server/index.cjs.js +349 -0
  19. package/dist/server/index.d.ts +32 -0
  20. package/dist/server/index.esm.js +344 -0
  21. package/dist/server/oauth.d.ts +24 -0
  22. package/dist/server/types.d.ts +136 -0
  23. package/dist/svelte.cjs.js +1 -1
  24. package/dist/svelte.d.ts +14 -4
  25. package/dist/svelte.esm.js +1 -1
  26. package/dist/types.d.ts +80 -0
  27. package/dist/useOneConnect.d.ts +13 -2
  28. package/dist/vue.cjs.js +1 -1
  29. package/dist/vue.d.ts +20 -18
  30. package/dist/vue.esm.js +1 -1
  31. package/dist/wrapper-options.d.ts +20 -15
  32. package/package.json +37 -15
  33. package/skills/one-connect/SKILL.md +194 -0
  34. package/src/button.ts +106 -157
  35. package/src/constants.ts +10 -4
  36. package/src/index.ts +17 -2
  37. package/src/next.ts +133 -0
  38. package/src/node.ts +107 -0
  39. package/src/platforms.ts +89 -0
  40. package/src/react.ts +23 -66
  41. package/src/return.ts +30 -0
  42. package/src/server/index.ts +382 -0
  43. package/src/server/oauth.ts +98 -0
  44. package/src/server/types.ts +155 -0
  45. package/src/svelte.ts +15 -15
  46. package/src/types.ts +87 -0
  47. package/src/useOneConnect.ts +36 -69
  48. package/src/vue.ts +23 -29
  49. package/src/wrapper-options.ts +46 -23
  50. package/dist/index.umd.js +0 -1
  51. package/dist/types/index.d.ts +0 -92
  52. package/src/react-types.d.ts +0 -23
  53. package/src/svelte-types.d.ts +0 -27
  54. package/src/types/index.d.ts +0 -92
  55. package/src/vue-types.d.ts +0 -16
@@ -0,0 +1,344 @@
1
+ import { randomBytes, createHash } from 'node:crypto';
2
+
3
+ /** Query params the app's callback route puts on its final redirect.
4
+ * The SDK reads them off the page URL when the tab comes home, so
5
+ * there is no completion page to build. */
6
+ const RETURN_STATUS_PARAM = "one_connect";
7
+ const RETURN_MESSAGE_PARAM = "one_connect_message";
8
+
9
+ /** One's production API. Point `oneApiUrl` elsewhere for development. */
10
+ const DEFAULT_ONE_API_URL = "https://api.withone.ai";
11
+
12
+ /** One cookie per flow, named by its state, so a retry or a second tab
13
+ * never overwrites the verifier of a flow still in progress. */
14
+ const TX_COOKIE_PREFIX = "one_tx_";
15
+
16
+ /** One gives the consent 10 minutes and then the code 10 minutes, so a
17
+ * legitimate callback can arrive up to twenty minutes after the start.
18
+ * Thirty minutes leaves a margin. */
19
+ const TX_COOKIE_MAX_AGE_SECONDS = 1800;
20
+ const DEFAULT_SCOPES = ["user:connections:read", "user:connections:write", "org:connections:read", "org:connections:write", "project:connections:read", "project:connections:write"];
21
+ function createState() {
22
+ return randomBytes(16).toString("hex");
23
+ }
24
+ function createPkceVerifier() {
25
+ return randomBytes(32).toString("base64url");
26
+ }
27
+ function pkceChallenge(verifier) {
28
+ return createHash("sha256").update(verifier).digest("base64url");
29
+ }
30
+ function txCookieName(state) {
31
+ return `${TX_COOKIE_PREFIX}${state}`;
32
+ }
33
+
34
+ /** The cookie scoped to the routes that need it: the directory of the
35
+ * callback path, which is also where the authorize route lives. */
36
+ function txCookie(state, verifier, redirectUri) {
37
+ const url = new URL(redirectUri);
38
+ const path = url.pathname.replace(/\/[^/]*$/, "") || "/";
39
+ return {
40
+ name: txCookieName(state),
41
+ value: verifier,
42
+ options: {
43
+ httpOnly: true,
44
+ secure: url.protocol === "https:",
45
+ sameSite: "lax",
46
+ maxAge: TX_COOKIE_MAX_AGE_SECONDS,
47
+ path
48
+ }
49
+ };
50
+ }
51
+ function basicAuthorization(clientId, clientSecret) {
52
+ return `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
53
+ }
54
+
55
+ /** The tenant headers a grant needs on every call. A grant made into an
56
+ * organization or project lives there; One resolves the tenant from
57
+ * these headers, and without them the call runs in the user's personal
58
+ * space. The access token's claims name the tenant, so echo them. */
59
+ function tenancyHeaders(accessToken) {
60
+ try {
61
+ const payload = JSON.parse(Buffer.from(accessToken.split(".")[1], "base64url").toString());
62
+ const headers = {};
63
+ const organizationId = payload.organization_ids?.[0];
64
+ const projectId = payload.project_ids?.[0];
65
+ if (organizationId) headers["X-One-Organization-Id"] = organizationId;
66
+ if (projectId) headers["X-One-Project-Id"] = projectId;
67
+ return headers;
68
+ } catch {
69
+ return {};
70
+ }
71
+ }
72
+
73
+ /** The scopes the token was granted, from its claims. Display only. */
74
+ function tokenScopes(accessToken) {
75
+ try {
76
+ const payload = JSON.parse(Buffer.from(accessToken.split(".")[1], "base64url").toString());
77
+ return (payload.scope ?? "").split(" ").filter(Boolean);
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Types for `@withone/connect/server`: the half of the SDK that runs on
85
+ * the app's server and holds the client secret.
86
+ */
87
+
88
+ /** What the app stores per user after the exchange. */
89
+
90
+ /**
91
+ * Where the app keeps each user's tokens: its database, a cache, an
92
+ * encrypted cookie. The SDK never sees a token outside these three
93
+ * calls. `userId` is the app's own id for its user.
94
+ */
95
+
96
+ /** The transaction cookie the authorize leg sets and the callback reads. */
97
+
98
+ /** One connection the grant reaches, with the access it confers. */
99
+
100
+ /** One catalog action for a platform. */
101
+
102
+ class OneConnectError extends Error {
103
+ constructor(code, message, status) {
104
+ super(message);
105
+ this.name = "OneConnectError";
106
+ this.code = code;
107
+ this.status = status;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * `@withone/connect/server`: the half of One Connect that runs on the
113
+ * app's server. It starts the flow, completes it, keeps the tokens fresh
114
+ * and makes every call with the grant. The client secret never leaves
115
+ * here.
116
+ *
117
+ * const oneConnect = createOneConnect({ clientId, clientSecret,
118
+ * redirectUri, permissionSet, tokenStore });
119
+ *
120
+ * // in the authorize route
121
+ * const { redirectUrl, cookie } = oneConnect.startAuthorization({ loginHint });
122
+ * // in the callback route
123
+ * const result = await oneConnect.completeAuthorization({ userId, url, getCookie });
124
+ * // afterwards
125
+ * const rows = await oneConnect.listConnections(userId);
126
+ * const reply = await oneConnect.runAction(userId, { connectionKey, actionId, method, path });
127
+ *
128
+ * The Next.js and Node adapters turn the first two into route handlers.
129
+ */
130
+
131
+ /** Refresh this long before expiry, so a call never races the clock. */
132
+ const REFRESH_MARGIN_MS = 60_000;
133
+ const CATALOG_PAGE_SIZE = 100;
134
+ const CATALOG_MAX_PAGES = 20;
135
+ function createOneConnect(config) {
136
+ const oneApiUrl = (config.oneApiUrl ?? DEFAULT_ONE_API_URL).replace(/\/+$/, "");
137
+ const authorizeUrl = `${oneApiUrl}/oauth/authorize`;
138
+ const tokenUrl = `${oneApiUrl}/oauth/token`;
139
+ const apiUrl = `${oneApiUrl}/v1`;
140
+ const returnTo = config.returnTo ?? "/";
141
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
142
+ const {
143
+ tokenStore
144
+ } = config;
145
+
146
+ /** One refresh in flight per user: two concurrent refreshes with the
147
+ * same refresh token trip One's reuse detection. */
148
+ const refreshing = new Map();
149
+ const returnUrl = (status, message) => {
150
+ const url = new URL(returnTo, config.redirectUri);
151
+ url.searchParams.set(RETURN_STATUS_PARAM, status);
152
+ if (message) url.searchParams.set(RETURN_MESSAGE_PARAM, message);
153
+ return url.toString();
154
+ };
155
+ const exchange = async body => {
156
+ const response = await fetch(tokenUrl, {
157
+ method: "POST",
158
+ headers: {
159
+ Authorization: basicAuthorization(config.clientId, config.clientSecret),
160
+ "Content-Type": "application/x-www-form-urlencoded"
161
+ },
162
+ body
163
+ });
164
+ if (!response.ok) {
165
+ throw new OneConnectError("request_failed", `One refused the token request (HTTP ${response.status}).`, response.status);
166
+ }
167
+ return await response.json();
168
+ };
169
+ const toTokens = response => ({
170
+ accessToken: response.access_token,
171
+ refreshToken: response.refresh_token,
172
+ expiresAt: Date.now() + response.expires_in * 1000
173
+ });
174
+ const startAuthorization = (input = {}) => {
175
+ const state = createState();
176
+ const verifier = createPkceVerifier();
177
+ const url = new URL(authorizeUrl);
178
+ url.searchParams.set("client_id", config.clientId);
179
+ url.searchParams.set("redirect_uri", config.redirectUri);
180
+ url.searchParams.set("response_type", "code");
181
+ url.searchParams.set("scope", scopes.join(" "));
182
+ url.searchParams.set("state", state);
183
+ url.searchParams.set("code_challenge", pkceChallenge(verifier));
184
+ url.searchParams.set("code_challenge_method", "S256");
185
+ if (config.permissionSet) url.searchParams.set("permission_set", config.permissionSet);
186
+ if (input.loginHint) url.searchParams.set("login_hint", input.loginHint);
187
+ return {
188
+ redirectUrl: url.toString(),
189
+ cookie: txCookie(state, verifier, config.redirectUri)
190
+ };
191
+ };
192
+ const completeAuthorization = async input => {
193
+ const params = new URL(input.url).searchParams;
194
+ const code = params.get("code");
195
+ const state = params.get("state");
196
+ const oauthError = params.get("error");
197
+ const cookieName = state ? txCookieName(state) : undefined;
198
+ const verifier = cookieName ? input.getCookie(cookieName) : undefined;
199
+ const fail = (outcome, message) => ({
200
+ outcome,
201
+ message,
202
+ redirectUrl: returnUrl("error", message),
203
+ clearCookieName: cookieName
204
+ });
205
+ if (oauthError === "access_denied") return fail("declined", "You cancelled the request.");
206
+ if (oauthError) return fail("failed", `One reported an error: ${oauthError}.`);
207
+ // The returned state names its own cookie. No cookie means a forged
208
+ // or stale state; the code is never exchanged in that case.
209
+ if (!code || !state || !verifier) return fail("failed", "The sign-in attempt expired or was tampered with.");
210
+ try {
211
+ const tokens = toTokens(await exchange(new URLSearchParams({
212
+ grant_type: "authorization_code",
213
+ code,
214
+ redirect_uri: config.redirectUri,
215
+ code_verifier: verifier
216
+ })));
217
+ await tokenStore.saveTokens(input.userId, tokens);
218
+ } catch (error) {
219
+ const status = error instanceof OneConnectError ? error.status : undefined;
220
+ return fail("failed", status ? `One rejected the code exchange (HTTP ${status}).` : "One could not be reached to complete the connection.");
221
+ }
222
+ return {
223
+ outcome: "connected",
224
+ redirectUrl: returnUrl("success"),
225
+ clearCookieName: cookieName
226
+ };
227
+ };
228
+ const refreshTokens = userId => {
229
+ const inFlight = refreshing.get(userId);
230
+ if (inFlight) return inFlight;
231
+ const job = (async () => {
232
+ const current = await tokenStore.loadTokens(userId);
233
+ if (!current) throw new OneConnectError("not_connected", "This user is not connected.");
234
+ try {
235
+ const next = toTokens(await exchange(new URLSearchParams({
236
+ grant_type: "refresh_token",
237
+ refresh_token: current.refreshToken
238
+ })));
239
+ // Both tokens: One rotates the pair on every refresh.
240
+ await tokenStore.saveTokens(userId, next);
241
+ return next;
242
+ } catch (error) {
243
+ // The family is dead: revoked, expired or reused. Keeping the
244
+ // pair would only fail again; the user has to reconnect.
245
+ await tokenStore.clearTokens(userId);
246
+ const status = error instanceof OneConnectError ? error.status : undefined;
247
+ throw new OneConnectError("refresh_failed", "The connection to One has expired or was revoked. Ask the user to connect again.", status);
248
+ } finally {
249
+ refreshing.delete(userId);
250
+ }
251
+ })();
252
+ refreshing.set(userId, job);
253
+ return job;
254
+ };
255
+ const getAccessToken = async userId => {
256
+ const tokens = await tokenStore.loadTokens(userId);
257
+ if (!tokens) throw new OneConnectError("not_connected", "This user is not connected.");
258
+ if (Date.now() < tokens.expiresAt - REFRESH_MARGIN_MS) return tokens.accessToken;
259
+ return (await refreshTokens(userId)).accessToken;
260
+ };
261
+ const oneFetch = async (userId, path, init = {}) => {
262
+ const accessToken = await getAccessToken(userId);
263
+ const headers = new Headers(init.headers);
264
+ headers.set("Authorization", `Bearer ${accessToken}`);
265
+ for (const [name, value] of Object.entries(tenancyHeaders(accessToken))) headers.set(name, value);
266
+ return fetch(`${apiUrl}${path.startsWith("/") ? path : `/${path}`}`, {
267
+ ...init,
268
+ headers
269
+ });
270
+ };
271
+ const listConnections = async userId => {
272
+ const response = await oneFetch(userId, "/connections/reachable");
273
+ if (!response.ok) {
274
+ throw new OneConnectError("request_failed", `One refused the connections request (HTTP ${response.status}).`, response.status);
275
+ }
276
+ const body = await response.json();
277
+ return body.rows ?? [];
278
+ };
279
+ const listActions = async (userId, platform) => {
280
+ const pageUrl = page => `/knowledge?connectionPlatform=${encodeURIComponent(platform)}&limit=${CATALOG_PAGE_SIZE}&page=${page}`;
281
+ const slim = rows => rows.map(row => ({
282
+ _id: String(row._id ?? ""),
283
+ title: String(row.title ?? ""),
284
+ method: String(row.method ?? ""),
285
+ path: String(row.path ?? "")
286
+ }));
287
+ const first = await oneFetch(userId, pageUrl(1));
288
+ if (!first.ok) {
289
+ throw new OneConnectError("request_failed", `One refused the catalog request (HTTP ${first.status}).`, first.status);
290
+ }
291
+ const page1 = await first.json();
292
+ const pages = Math.min(Math.max(page1.pages ?? 1, 1), CATALOG_MAX_PAGES);
293
+ const rest = await Promise.all(Array.from({
294
+ length: pages - 1
295
+ }, (_, index) => oneFetch(userId, pageUrl(index + 2)).then(response => response.ok ? response.json() : null).catch(() => null)));
296
+ return slim([page1, ...rest].flatMap(page => page?.rows ?? []));
297
+ };
298
+ const runAction = async (userId, input) => {
299
+ const method = input.method.toUpperCase();
300
+ const query = input.query ? `?${new URLSearchParams(input.query)}` : "";
301
+ const headers = {
302
+ "x-one-connection-key": input.connectionKey,
303
+ "x-one-action-id": input.actionId
304
+ };
305
+ const hasBody = method !== "GET" && method !== "HEAD" && input.body !== undefined;
306
+ if (hasBody) headers["Content-Type"] = "application/json";
307
+ const response = await oneFetch(userId, `/passthrough${input.path}${query}`, {
308
+ method,
309
+ headers,
310
+ body: hasBody ? JSON.stringify(input.body) : undefined
311
+ });
312
+ const text = await response.text();
313
+ let data = text;
314
+ try {
315
+ data = JSON.parse(text);
316
+ } catch {
317
+ /* the provider answered with something other than JSON */
318
+ }
319
+ // One's own refusals carry a correlationId; a provider's error does
320
+ // not. A One-shaped 403 is the grant working, not the provider.
321
+ const blockedByGrant = !response.ok && typeof data === "object" && data !== null && "correlationId" in data;
322
+ return {
323
+ status: response.status,
324
+ ok: response.ok,
325
+ blockedByGrant,
326
+ data
327
+ };
328
+ };
329
+ return {
330
+ startAuthorization,
331
+ completeAuthorization,
332
+ isConnected: async userId => (await tokenStore.loadTokens(userId)) !== null,
333
+ getAccessToken,
334
+ getTokens: userId => tokenStore.loadTokens(userId),
335
+ refreshTokens,
336
+ disconnect: userId => tokenStore.clearTokens(userId),
337
+ listConnections,
338
+ listActions,
339
+ runAction,
340
+ fetch: oneFetch
341
+ };
342
+ }
343
+
344
+ export { OneConnectError, createOneConnect, tenancyHeaders, tokenScopes };
@@ -0,0 +1,24 @@
1
+ import type { OneConnectCookie } from "./types";
2
+ /** One cookie per flow, named by its state, so a retry or a second tab
3
+ * never overwrites the verifier of a flow still in progress. */
4
+ export declare const TX_COOKIE_PREFIX = "one_tx_";
5
+ /** One gives the consent 10 minutes and then the code 10 minutes, so a
6
+ * legitimate callback can arrive up to twenty minutes after the start.
7
+ * Thirty minutes leaves a margin. */
8
+ export declare const TX_COOKIE_MAX_AGE_SECONDS = 1800;
9
+ export declare const DEFAULT_SCOPES: string[];
10
+ export declare function createState(): string;
11
+ export declare function createPkceVerifier(): string;
12
+ export declare function pkceChallenge(verifier: string): string;
13
+ export declare function txCookieName(state: string): string;
14
+ /** The cookie scoped to the routes that need it: the directory of the
15
+ * callback path, which is also where the authorize route lives. */
16
+ export declare function txCookie(state: string, verifier: string, redirectUri: string): OneConnectCookie;
17
+ export declare function basicAuthorization(clientId: string, clientSecret: string): string;
18
+ /** The tenant headers a grant needs on every call. A grant made into an
19
+ * organization or project lives there; One resolves the tenant from
20
+ * these headers, and without them the call runs in the user's personal
21
+ * space. The access token's claims name the tenant, so echo them. */
22
+ export declare function tenancyHeaders(accessToken: string): Record<string, string>;
23
+ /** The scopes the token was granted, from its claims. Display only. */
24
+ export declare function tokenScopes(accessToken: string): string[];
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Types for `@withone/connect/server`: the half of the SDK that runs on
3
+ * the app's server and holds the client secret.
4
+ */
5
+ /** What the app stores per user after the exchange. */
6
+ export interface OneConnectTokens {
7
+ accessToken: string;
8
+ refreshToken: string;
9
+ /** Epoch milliseconds when the access token expires. */
10
+ expiresAt: number;
11
+ }
12
+ /**
13
+ * Where the app keeps each user's tokens: its database, a cache, an
14
+ * encrypted cookie. The SDK never sees a token outside these three
15
+ * calls. `userId` is the app's own id for its user.
16
+ */
17
+ export interface OneConnectTokenStore {
18
+ saveTokens: (userId: string, tokens: OneConnectTokens) => Promise<void>;
19
+ loadTokens: (userId: string) => Promise<OneConnectTokens | null>;
20
+ clearTokens: (userId: string) => Promise<void>;
21
+ }
22
+ export interface OneConnectServerConfig {
23
+ /** The app's client id from the dashboard. */
24
+ clientId: string;
25
+ /** The app's client secret. Server only. */
26
+ clientSecret: string;
27
+ /** Exactly the callback URL registered on the app, character for
28
+ * character, for example "https://yourapp.com/api/one/callback". */
29
+ redirectUri: string;
30
+ /** The ask (permission set) to open the consent card on. Omit to ask
31
+ * for everything the user has connected. */
32
+ permissionSet?: string;
33
+ /** One's API origin. Production when omitted. */
34
+ oneApiUrl?: string;
35
+ /** Where the callback sends the browser afterwards. "/" when omitted;
36
+ * the outcome is appended as `?one_connect=…`. */
37
+ returnTo?: string;
38
+ /** OAuth scopes. All three tenancy tiers when omitted, so the user may
39
+ * grant from any space. */
40
+ scopes?: string[];
41
+ tokenStore: OneConnectTokenStore;
42
+ }
43
+ /** The transaction cookie the authorize leg sets and the callback reads. */
44
+ export interface OneConnectCookie {
45
+ name: string;
46
+ value: string;
47
+ options: {
48
+ httpOnly: true;
49
+ secure: boolean;
50
+ sameSite: "lax";
51
+ maxAge: number;
52
+ path: string;
53
+ };
54
+ }
55
+ export interface StartAuthorizationInput {
56
+ /** Pre-fills the sign-in email on One's page. Never locks it. */
57
+ loginHint?: string;
58
+ }
59
+ export interface StartAuthorizationResult {
60
+ /** Send the browser here with a 302. */
61
+ redirectUrl: string;
62
+ /** Set this cookie on the same response. */
63
+ cookie: OneConnectCookie;
64
+ }
65
+ export interface CompleteAuthorizationInput {
66
+ /** The app's user the grant belongs to. */
67
+ userId: string;
68
+ /** The full callback URL One redirected to, with its query string. */
69
+ url: string;
70
+ /** Reads a cookie by name from the incoming request. */
71
+ getCookie: (name: string) => string | undefined;
72
+ }
73
+ export type AuthorizationOutcome = "connected" | "declined" | "failed";
74
+ export interface CompleteAuthorizationResult {
75
+ outcome: AuthorizationOutcome;
76
+ /** Safe to show to the user when the outcome is not "connected". */
77
+ message?: string;
78
+ /** Send the browser here with a 302; it carries `?one_connect=…`. */
79
+ redirectUrl: string;
80
+ /** Delete this cookie on the same response, when one was involved. */
81
+ clearCookieName?: string;
82
+ }
83
+ /** One connection the grant reaches, with the access it confers. */
84
+ export interface ReachableConnection {
85
+ key: string;
86
+ platform: string;
87
+ name?: string;
88
+ title?: string;
89
+ image?: string;
90
+ access: ConnectionAccess;
91
+ }
92
+ export type ConnectionAccess = {
93
+ policy: "full";
94
+ } | {
95
+ policy: "methods";
96
+ methods: string[];
97
+ } | {
98
+ policy: "actions";
99
+ actions: {
100
+ actionId: string;
101
+ title: string;
102
+ method: string;
103
+ }[];
104
+ };
105
+ /** One catalog action for a platform. */
106
+ export interface PlatformAction {
107
+ _id: string;
108
+ title: string;
109
+ method: string;
110
+ path: string;
111
+ }
112
+ export interface RunActionInput {
113
+ /** From `listConnections`. */
114
+ connectionKey: string;
115
+ /** From `listActions`. */
116
+ actionId: string;
117
+ method: string;
118
+ /** The action's path, appended to /v1/passthrough. */
119
+ path: string;
120
+ body?: unknown;
121
+ query?: Record<string, string>;
122
+ }
123
+ export interface RunActionResult {
124
+ status: number;
125
+ ok: boolean;
126
+ /** True when One refused the call because it is outside the grant.
127
+ * The provider was never called. Do not retry. */
128
+ blockedByGrant: boolean;
129
+ data: unknown;
130
+ }
131
+ export type OneConnectErrorCode = "not_connected" | "refresh_failed" | "request_failed";
132
+ export declare class OneConnectError extends Error {
133
+ readonly code: OneConnectErrorCode;
134
+ readonly status?: number;
135
+ constructor(code: OneConnectErrorCode, message: string, status?: number);
136
+ }
@@ -1 +1 @@
1
- "use strict";function e(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 t="one_connect",n="one_connect_message";let o=!1;const i=e=>{let i=!1;if("undefined"!=typeof window&&!o){const r=new URLSearchParams(window.location.search),l=r.get(t);if("success"===l||"error"===l){var c;o=!0;const s=null!==(c=r.get(n))&&void 0!==c?c:void 0,a=()=>{const e=new URLSearchParams(window.location.search);(e.has(t)||e.has(n))&&(e.delete(t),e.delete(n),window.history.replaceState(null,"",window.location.pathname+(e.toString()?`?${e.toString()}`:"")+window.location.hash))};a();for(const e of[50,500,2e3])window.setTimeout(a,e);window.setTimeout(()=>{if(!i){i=!0;try{var t,n;"success"===l?null===(t=e.onSuccess)||void 0===t||t.call(e):null===(n=e.onError)||void 0===n||n.call(e,null!=s?s:"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=>{}}},c="one-connect-button-styles",r="cubic-bezier(.2,.9,.25,1)",l={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,o;!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 ${r},box-shadow .2s ${r},opacity .2s ${r}}`,".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 ${r};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 ${r},opacity .2s ${r}}`,".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:l,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",w=!1;const v=i({...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"),v.open())});const x=()=>{var e,n,o;const i="accent"===m,c="block"===m,r=c?h.surface:i?null!==(e=t.accentColor)&&void 0!==e?e:a:h.btnBg,l=c?h.fg:i?p:h.btnFg,s=c?h.surface:r,w=p;g.style.background="connected"!==b||c?r:d,g.style.color="connected"!==b||c?l:p,g.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,g.disabled="connecting"===b,g.innerHTML="";const v=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 r=document.createElement("span");r.className="owcb-stack",r.setAttribute("aria-hidden","true");for(const e of c.slice(0,3)){var l;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!==(l=e.imageUrl)&&void 0!==l?l: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();r.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}`,r.appendChild(e)}return r}(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 v&&e.appendChild(v);if(g.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,g.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 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 v&&g.appendChild(v);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=>{w||(b=e,x())};return x(),e.appendChild(g),{setState:y,destroy:()=>{w=!0,v.close({keepResult:!0}),g.remove()}}};function m(e,t){const n=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}}!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class t extends HTMLElement{constructor(...t){super(...t),e(this,"onSuccess",null),e(this,"onError",null),e(this,"onClose",null),e(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,r,l;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!==(r=this.getAttribute("accent-color"))&&void 0!==r?r:void 0,connectedLabel:null!==(l=this.getAttribute("connected-label"))&&void 0!==l?l:void 0})}}e(t,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",t)}}(),exports.connectButton=function(e,t){let n=h(e,m(t));return{update(t){n.destroy(),n=h(e,m(t))},destroy(){n.destroy()}}};
1
+ "use strict";var t=require("@withone/connect");exports.connectButton=function(o,n){let e=t.mountConnectButton(o,t.optionsFromProps(n));return{update(n){e.destroy(),e=t.mountConnectButton(o,t.optionsFromProps(n))},destroy(){e.destroy()}}};
package/dist/svelte.d.ts CHANGED
@@ -1,6 +1,16 @@
1
- import { type ConnectButtonWrapperProps } from "./wrapper-options";
2
- export type { ConnectButtonWrapperProps as ConnectButtonProps };
3
- export declare function connectButton(node: HTMLElement, props: ConnectButtonWrapperProps): {
4
- update: (next: ConnectButtonWrapperProps) => void;
1
+ /**
2
+ * Svelte: `import { connectButton } from "@withone/connect/svelte"`.
3
+ *
4
+ * A Svelte action, the idiomatic shape for DOM-mounting libraries, so no
5
+ * Svelte compiler or dependency is involved:
6
+ *
7
+ * <div use:connectButton={{ authorizeUrl: "/api/one/authorize",
8
+ * platforms: ["stripe", "notion"],
9
+ * onSuccess: () => { ... } }} />
10
+ */
11
+ import { type ConnectButtonProps } from "@withone/connect";
12
+ export type { ConnectButtonProps };
13
+ export declare function connectButton(node: HTMLElement, props: ConnectButtonProps): {
14
+ update: (next: ConnectButtonProps) => void;
5
15
  destroy: () => void;
6
16
  };
@@ -1 +1 @@
1
- function e(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 t="one_connect",n="one_connect_message";let o=!1;const i=e=>{let i=!1;if("undefined"!=typeof window&&!o){const r=new URLSearchParams(window.location.search),l=r.get(t);if("success"===l||"error"===l){var c;o=!0;const s=null!==(c=r.get(n))&&void 0!==c?c:void 0,a=()=>{const e=new URLSearchParams(window.location.search);(e.has(t)||e.has(n))&&(e.delete(t),e.delete(n),window.history.replaceState(null,"",window.location.pathname+(e.toString()?`?${e.toString()}`:"")+window.location.hash))};a();for(const e of[50,500,2e3])window.setTimeout(a,e);window.setTimeout(()=>{if(!i){i=!0;try{var t,n;"success"===l?null===(t=e.onSuccess)||void 0===t||t.call(e):null===(n=e.onError)||void 0===n||n.call(e,null!=s?s:"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=>{}}},c="one-connect-button-styles",r="cubic-bezier(.2,.9,.25,1)",l={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,o;!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 ${r},box-shadow .2s ${r},opacity .2s ${r}}`,".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 ${r};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 ${r},opacity .2s ${r}}`,".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:l,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",w=!1;const v=i({...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"),v.open())});const x=()=>{var e,n,o;const i="accent"===m,c="block"===m,r=c?h.surface:i?null!==(e=t.accentColor)&&void 0!==e?e:a:h.btnBg,l=c?h.fg:i?p:h.btnFg,s=c?h.surface:r,w=p;g.style.background="connected"!==b||c?r:d,g.style.color="connected"!==b||c?l:p,g.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,g.disabled="connecting"===b,g.innerHTML="";const v=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 r=document.createElement("span");r.className="owcb-stack",r.setAttribute("aria-hidden","true");for(const e of c.slice(0,3)){var l;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!==(l=e.imageUrl)&&void 0!==l?l: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();r.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}`,r.appendChild(e)}return r}(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 v&&e.appendChild(v);if(g.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,g.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 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 v&&g.appendChild(v);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=>{w||(b=e,x())};return x(),e.appendChild(g),{setState:y,destroy:()=>{w=!0,v.close({keepResult:!0}),g.remove()}}};function m(e,t){const n=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}}function f(e,t){let n=h(e,m(t));return{update(t){n.destroy(),n=h(e,m(t))},destroy(){n.destroy()}}}!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class t extends HTMLElement{constructor(...t){super(...t),e(this,"onSuccess",null),e(this,"onError",null),e(this,"onClose",null),e(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,r,l;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!==(r=this.getAttribute("accent-color"))&&void 0!==r?r:void 0,connectedLabel:null!==(l=this.getAttribute("connected-label"))&&void 0!==l?l:void 0})}}e(t,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",t)}}();export{f as connectButton};
1
+ import{mountConnectButton as t,optionsFromProps as e}from"@withone/connect";function o(o,r){let n=t(o,e(r));return{update(r){n.destroy(),n=t(o,e(r))},destroy(){n.destroy()}}}export{o as connectButton};
@@ -0,0 +1,80 @@
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
+ /** Theme of One's hosted connect page. */
10
+ export type OneConnectTheme = "light" | "dark";
11
+ export interface OneConnectOptions {
12
+ /** The app's own backend authorize route. Relative paths such as
13
+ * "/api/one/authorize" resolve against the page's origin. */
14
+ authorizeUrl: string;
15
+ /** Theme for One's hosted page. Carried on the URL fragment, which
16
+ * survives the redirect chain, so the backend forwards nothing. */
17
+ appTheme?: OneConnectTheme;
18
+ /** The grant completed and the backend stored the tokens. */
19
+ onSuccess?: () => void;
20
+ /** The flow ended without a grant: the user declined, the attempt
21
+ * expired, or the exchange failed. `message` is safe to show. */
22
+ onError?: (message: string) => void;
23
+ }
24
+ export interface OneConnectHandle {
25
+ /** Navigates the tab to One's hosted connect flow. */
26
+ open: () => void;
27
+ }
28
+ /** How the app's callback route reports the outcome on its final
29
+ * redirect, read off the page URL when the tab returns. */
30
+ export interface OneConnectReturn {
31
+ status: "success" | "error";
32
+ message?: string;
33
+ }
34
+ /**
35
+ * A connector chip on the button. Pass One's connector slug ("stripe",
36
+ * "google-calendar") and the SDK shows the logo and the name; pass an
37
+ * object to override either.
38
+ */
39
+ export type ConnectButtonPlatformInput = string | {
40
+ slug?: string;
41
+ name?: string;
42
+ imageUrl?: string;
43
+ };
44
+ /** A normalized chip: what the button actually draws. */
45
+ export interface ConnectButtonPlatform {
46
+ slug: string;
47
+ name: string;
48
+ imageUrl: string;
49
+ }
50
+ export type ConnectButtonVariant = "default" | "accent" | "block";
51
+ export type ConnectButtonState = "idle" | "connecting" | "connected";
52
+ export interface ConnectButtonOptions {
53
+ /** Everything the flow needs; the button wires open() and the
54
+ * Connecting and Connected states around your callbacks. */
55
+ connect: OneConnectOptions;
56
+ /** "Connect your apps" unless overridden. */
57
+ label?: string;
58
+ /** default = neutral pill; accent = brand-colored pill; block =
59
+ * full-width card with a description and a "Secured by One" foot. */
60
+ variant?: ConnectButtonVariant;
61
+ /** Matches the host page, not One's page (that is connect.appTheme). */
62
+ theme?: OneConnectTheme;
63
+ /** Connector chips. The first three render; the rest fold into the
64
+ * "+N" chip together with moreCount. */
65
+ platforms?: ConnectButtonPlatformInput[];
66
+ /** Extra count for the "+N" chip, e.g. 274 for "the whole catalog". */
67
+ moreCount?: number;
68
+ /** Sub-line on the block variant, shown while idle. */
69
+ description?: string;
70
+ /** Fill of the accent variant; One's lime when omitted. */
71
+ accentColor?: string;
72
+ /** Label for the connected state. */
73
+ connectedLabel?: string;
74
+ }
75
+ export interface ConnectButtonHandle {
76
+ /** Override the visual state by hand. */
77
+ setState: (state: ConnectButtonState) => void;
78
+ /** Remove the button. */
79
+ destroy: () => void;
80
+ }