@withone/connect 0.9.0 → 0.11.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 +88 -7
- package/dist/button.d.ts +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/server/index.cjs.js +145 -35
- package/dist/server/index.d.ts +11 -3
- package/dist/server/index.esm.js +145 -36
- package/dist/server/oauth.d.ts +4 -0
- package/dist/server/types.d.ts +41 -3
- package/dist/types.d.ts +2 -4
- package/dist/vue.cjs.js +1 -1
- package/dist/vue.d.ts +0 -9
- package/dist/vue.esm.js +1 -1
- package/dist/wrapper-options.d.ts +0 -1
- package/package.json +1 -1
- package/skills/one-connect/SKILL.md +22 -4
- package/src/button.ts +13 -14
- package/src/server/index.ts +176 -41
- package/src/server/oauth.ts +16 -0
- package/src/server/types.ts +42 -3
- package/src/types.ts +3 -6
- package/src/vue.ts +0 -1
- package/src/wrapper-options.ts +0 -3
package/src/server/index.ts
CHANGED
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
* const reply = await oneConnect.runAction(userId, { connectionKey, actionId, method, path });
|
|
17
17
|
*
|
|
18
18
|
* The Next.js and Node adapters turn the first two into route handlers.
|
|
19
|
+
*
|
|
20
|
+
* Refresh. One's access token lives an hour by default, its refresh token 30
|
|
21
|
+
* days; every refresh rotates both, and One treats a second use of a
|
|
22
|
+
* rotated refresh token as theft and revokes the whole grant. So the
|
|
23
|
+
* client refreshes one user at a time (in this process always, across
|
|
24
|
+
* processes through `tokenStore.withLock`), re-reads the store before
|
|
25
|
+
* spending a refresh token, clears tokens only when One declares the
|
|
26
|
+
* grant dead, and never lets a failing old pair delete a newer one.
|
|
19
27
|
*/
|
|
20
28
|
import { DEFAULT_ONE_API_URL, RETURN_MESSAGE_PARAM, RETURN_STATUS_PARAM } from "../constants";
|
|
21
29
|
import {
|
|
@@ -24,6 +32,7 @@ import {
|
|
|
24
32
|
createPkceVerifier,
|
|
25
33
|
createState,
|
|
26
34
|
pkceChallenge,
|
|
35
|
+
refreshTokenExpiresAt,
|
|
27
36
|
tenancyHeaders,
|
|
28
37
|
txCookie,
|
|
29
38
|
txCookieName,
|
|
@@ -36,6 +45,7 @@ import {
|
|
|
36
45
|
type OneConnectTokens,
|
|
37
46
|
type PlatformAction,
|
|
38
47
|
type ReachableConnection,
|
|
48
|
+
type RefreshIfExpiringOptions,
|
|
39
49
|
type RunActionInput,
|
|
40
50
|
type RunActionResult,
|
|
41
51
|
type StartAuthorizationInput,
|
|
@@ -43,7 +53,7 @@ import {
|
|
|
43
53
|
} from "./types";
|
|
44
54
|
|
|
45
55
|
export * from "./types";
|
|
46
|
-
export { tenancyHeaders, tokenScopes } from "./oauth";
|
|
56
|
+
export { refreshTokenExpiresAt, tenancyHeaders, tokenScopes } from "./oauth";
|
|
47
57
|
|
|
48
58
|
/** Refresh this long before expiry, so a call never races the clock. */
|
|
49
59
|
const REFRESH_MARGIN_MS = 60_000;
|
|
@@ -56,6 +66,16 @@ interface TokenResponse {
|
|
|
56
66
|
expires_in: number;
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
/** What One's token endpoint answered. A network failure throws instead. */
|
|
70
|
+
type TokenAnswer =
|
|
71
|
+
| { ok: true; body: TokenResponse }
|
|
72
|
+
| { ok: false; status: number; error?: string };
|
|
73
|
+
|
|
74
|
+
/** The one refusal that means the grant is gone for good: revoked by the
|
|
75
|
+
* user, expired, or burned by a reused refresh token (RFC 6749 §5.2). */
|
|
76
|
+
const isDeadGrant = (answer: TokenAnswer): boolean =>
|
|
77
|
+
!answer.ok && answer.status === 400 && answer.error === "invalid_grant";
|
|
78
|
+
|
|
59
79
|
export interface OneConnect {
|
|
60
80
|
/** The authorize leg: where to send the browser and the cookie to set. */
|
|
61
81
|
startAuthorization: (
|
|
@@ -73,8 +93,19 @@ export interface OneConnect {
|
|
|
73
93
|
getAccessToken: (userId: string) => Promise<string>;
|
|
74
94
|
/** The stored tokens, for display. Null when not connected. */
|
|
75
95
|
getTokens: (userId: string) => Promise<OneConnectTokens | null>;
|
|
76
|
-
/**
|
|
96
|
+
/** Refreshes now and returns the new pair. When another caller rotated
|
|
97
|
+
* the pair a moment earlier, returns that pair instead of rotating it
|
|
98
|
+
* a second time. */
|
|
77
99
|
refreshTokens: (userId: string) => Promise<OneConnectTokens>;
|
|
100
|
+
/** Refreshes only when the access token or the refresh token expires
|
|
101
|
+
* within `withinMs`, and returns the pair that is current afterwards.
|
|
102
|
+
* For background jobs: a frequent run keeps access tokens warm, and a
|
|
103
|
+
* daily run with a window of days keeps idle users' 30-day refresh
|
|
104
|
+
* tokens from running out. */
|
|
105
|
+
refreshIfExpiring: (
|
|
106
|
+
userId: string,
|
|
107
|
+
options?: RefreshIfExpiringOptions,
|
|
108
|
+
) => Promise<OneConnectTokens>;
|
|
78
109
|
/** Drops the app's copy of the tokens. The user revokes the grant
|
|
79
110
|
* itself from their One dashboard. */
|
|
80
111
|
disconnect: (userId: string) => Promise<void>;
|
|
@@ -113,7 +144,7 @@ export function createOneConnect(config: OneConnectServerConfig): OneConnect {
|
|
|
113
144
|
return url.toString();
|
|
114
145
|
};
|
|
115
146
|
|
|
116
|
-
const
|
|
147
|
+
const postToken = async (body: URLSearchParams): Promise<TokenAnswer> => {
|
|
117
148
|
const response = await fetch(tokenUrl, {
|
|
118
149
|
method: "POST",
|
|
119
150
|
headers: {
|
|
@@ -122,14 +153,43 @@ export function createOneConnect(config: OneConnectServerConfig): OneConnect {
|
|
|
122
153
|
},
|
|
123
154
|
body,
|
|
124
155
|
});
|
|
125
|
-
if (
|
|
156
|
+
if (response.ok)
|
|
157
|
+
return { ok: true, body: (await response.json()) as TokenResponse };
|
|
158
|
+
let error: string | undefined;
|
|
159
|
+
try {
|
|
160
|
+
const refusal = (await response.json()) as { error?: unknown };
|
|
161
|
+
if (typeof refusal.error === "string") error = refusal.error;
|
|
162
|
+
} catch {
|
|
163
|
+
/* not an OAuth error body: a proxy page or a server error */
|
|
164
|
+
}
|
|
165
|
+
return { ok: false, status: response.status, error };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const exchange = async (body: URLSearchParams): Promise<TokenResponse> => {
|
|
169
|
+
const answer = await postToken(body);
|
|
170
|
+
if (!answer.ok) {
|
|
126
171
|
throw new OneConnectError(
|
|
127
172
|
"request_failed",
|
|
128
|
-
`One refused the token request (HTTP ${
|
|
129
|
-
|
|
173
|
+
`One refused the token request (HTTP ${answer.status}).`,
|
|
174
|
+
answer.status,
|
|
130
175
|
);
|
|
131
176
|
}
|
|
132
|
-
return
|
|
177
|
+
return answer.body;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** Runs `run` under the app's cross-process lock for this user, when
|
|
181
|
+
* the store has one. */
|
|
182
|
+
const locked = <T>(userId: string, run: () => Promise<T>): Promise<T> =>
|
|
183
|
+
tokenStore.withLock ? tokenStore.withLock(userId, run) : run();
|
|
184
|
+
|
|
185
|
+
/** Whether either token of the pair stops working within `withinMs`. */
|
|
186
|
+
const expiresWithin = (tokens: OneConnectTokens, withinMs: number): boolean => {
|
|
187
|
+
const horizon = Date.now() + withinMs;
|
|
188
|
+
const refreshExpiresAt = refreshTokenExpiresAt(tokens.refreshToken);
|
|
189
|
+
return (
|
|
190
|
+
tokens.expiresAt <= horizon ||
|
|
191
|
+
(refreshExpiresAt !== null && refreshExpiresAt <= horizon)
|
|
192
|
+
);
|
|
133
193
|
};
|
|
134
194
|
|
|
135
195
|
const toTokens = (response: TokenResponse): OneConnectTokens => ({
|
|
@@ -200,7 +260,9 @@ export function createOneConnect(config: OneConnectServerConfig): OneConnect {
|
|
|
200
260
|
}),
|
|
201
261
|
),
|
|
202
262
|
);
|
|
203
|
-
|
|
263
|
+
// Under the lock, so a refresh in flight on another server cannot
|
|
264
|
+
// interleave with this save.
|
|
265
|
+
await locked(input.userId, () => tokenStore.saveTokens(input.userId, tokens));
|
|
204
266
|
} catch (error) {
|
|
205
267
|
const status = error instanceof OneConnectError ? error.status : undefined;
|
|
206
268
|
return fail(
|
|
@@ -218,51 +280,123 @@ export function createOneConnect(config: OneConnectServerConfig): OneConnect {
|
|
|
218
280
|
};
|
|
219
281
|
};
|
|
220
282
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
283
|
+
const notConnected = () =>
|
|
284
|
+
new OneConnectError("not_connected", "This user is not connected.");
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The grant behind `failed` is dead. Clears it, unless a newer pair
|
|
288
|
+
* landed while it was failing (a reconnect's callback, another
|
|
289
|
+
* server's refresh): that pair is returned instead, because the user
|
|
290
|
+
* did nothing wrong and deleting it would disconnect them.
|
|
291
|
+
*/
|
|
292
|
+
const retire = async (
|
|
293
|
+
userId: string,
|
|
294
|
+
failed: OneConnectTokens,
|
|
295
|
+
status?: number,
|
|
296
|
+
): Promise<OneConnectTokens> => {
|
|
297
|
+
const latest = await tokenStore.loadTokens(userId);
|
|
298
|
+
if (latest && latest.refreshToken !== failed.refreshToken) return latest;
|
|
299
|
+
await tokenStore.clearTokens(userId, failed);
|
|
300
|
+
throw new OneConnectError(
|
|
301
|
+
"refresh_failed",
|
|
302
|
+
"The connection to One has expired or was revoked. Ask the user to connect again.",
|
|
303
|
+
status,
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The one place a refresh token is spent. Under the app's lock it
|
|
309
|
+
* re-reads the store, and refreshes only when `stillNeeded` says the
|
|
310
|
+
* stored pair still needs it: another process may have refreshed while
|
|
311
|
+
* this one waited, and spending the same refresh token twice makes One
|
|
312
|
+
* revoke the grant.
|
|
313
|
+
*/
|
|
314
|
+
const refreshUnderLock = (
|
|
315
|
+
userId: string,
|
|
316
|
+
stillNeeded: (current: OneConnectTokens) => boolean,
|
|
317
|
+
): Promise<OneConnectTokens> =>
|
|
318
|
+
locked(userId, async () => {
|
|
225
319
|
const current = await tokenStore.loadTokens(userId);
|
|
226
|
-
if (!current)
|
|
227
|
-
|
|
320
|
+
if (!current) throw notConnected();
|
|
321
|
+
if (!stillNeeded(current)) return current;
|
|
322
|
+
|
|
323
|
+
// An expired refresh token cannot work, and One answers one with a
|
|
324
|
+
// server error rather than invalid_grant, so settle it here.
|
|
325
|
+
const refreshExpiresAt = refreshTokenExpiresAt(current.refreshToken);
|
|
326
|
+
if (refreshExpiresAt !== null && refreshExpiresAt <= Date.now())
|
|
327
|
+
return retire(userId, current);
|
|
328
|
+
|
|
329
|
+
let answer: TokenAnswer;
|
|
228
330
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
331
|
+
answer = await postToken(
|
|
332
|
+
new URLSearchParams({
|
|
333
|
+
grant_type: "refresh_token",
|
|
334
|
+
refresh_token: current.refreshToken,
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
} catch {
|
|
338
|
+
throw new OneConnectError(
|
|
339
|
+
"request_failed",
|
|
340
|
+
"One could not be reached to refresh the connection. The tokens were kept; try again.",
|
|
236
341
|
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (answer.ok) {
|
|
237
345
|
// Both tokens: One rotates the pair on every refresh.
|
|
346
|
+
const next = toTokens(answer.body);
|
|
238
347
|
await tokenStore.saveTokens(userId, next);
|
|
239
348
|
return next;
|
|
240
|
-
} catch (error) {
|
|
241
|
-
// The family is dead: revoked, expired or reused. Keeping the
|
|
242
|
-
// pair would only fail again; the user has to reconnect.
|
|
243
|
-
await tokenStore.clearTokens(userId);
|
|
244
|
-
const status = error instanceof OneConnectError ? error.status : undefined;
|
|
245
|
-
throw new OneConnectError(
|
|
246
|
-
"refresh_failed",
|
|
247
|
-
"The connection to One has expired or was revoked. Ask the user to connect again.",
|
|
248
|
-
status,
|
|
249
|
-
);
|
|
250
|
-
} finally {
|
|
251
|
-
refreshing.delete(userId);
|
|
252
349
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
350
|
+
if (isDeadGrant(answer)) return retire(userId, current, answer.status);
|
|
351
|
+
// A server error, a rate limit, a misconfigured secret: nothing says
|
|
352
|
+
// the grant is gone, so keep the tokens and let the caller retry.
|
|
353
|
+
throw new OneConnectError(
|
|
354
|
+
"request_failed",
|
|
355
|
+
`One could not refresh the connection (HTTP ${answer.status}). The tokens were kept; try again.`,
|
|
356
|
+
answer.status,
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
/** One refresh per user in this process; concurrent callers share it. */
|
|
361
|
+
const singleFlight = (
|
|
362
|
+
userId: string,
|
|
363
|
+
job: () => Promise<OneConnectTokens>,
|
|
364
|
+
): Promise<OneConnectTokens> => {
|
|
365
|
+
const inFlight = refreshing.get(userId);
|
|
366
|
+
if (inFlight) return inFlight;
|
|
367
|
+
const running = job().finally(() => refreshing.delete(userId));
|
|
368
|
+
refreshing.set(userId, running);
|
|
369
|
+
return running;
|
|
256
370
|
};
|
|
257
371
|
|
|
258
|
-
const
|
|
372
|
+
const refreshTokens = (userId: string): Promise<OneConnectTokens> =>
|
|
373
|
+
singleFlight(userId, async () => {
|
|
374
|
+
const before = await tokenStore.loadTokens(userId);
|
|
375
|
+
if (!before) throw notConnected();
|
|
376
|
+
// Rotate the pair seen now; a pair someone else rotated since is
|
|
377
|
+
// already fresh.
|
|
378
|
+
return refreshUnderLock(
|
|
379
|
+
userId,
|
|
380
|
+
(current) => current.refreshToken === before.refreshToken,
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const refreshIfExpiring = async (
|
|
385
|
+
userId: string,
|
|
386
|
+
options: RefreshIfExpiringOptions = {},
|
|
387
|
+
): Promise<OneConnectTokens> => {
|
|
388
|
+
const withinMs = options.withinMs ?? REFRESH_MARGIN_MS;
|
|
259
389
|
const tokens = await tokenStore.loadTokens(userId);
|
|
260
|
-
if (!tokens)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
390
|
+
if (!tokens) throw notConnected();
|
|
391
|
+
if (!expiresWithin(tokens, withinMs)) return tokens;
|
|
392
|
+
return singleFlight(userId, () =>
|
|
393
|
+
refreshUnderLock(userId, (current) => expiresWithin(current, withinMs)),
|
|
394
|
+
);
|
|
264
395
|
};
|
|
265
396
|
|
|
397
|
+
const getAccessToken = async (userId: string): Promise<string> =>
|
|
398
|
+
(await refreshIfExpiring(userId)).accessToken;
|
|
399
|
+
|
|
266
400
|
const oneFetch = async (
|
|
267
401
|
userId: string,
|
|
268
402
|
path: string,
|
|
@@ -373,6 +507,7 @@ export function createOneConnect(config: OneConnectServerConfig): OneConnect {
|
|
|
373
507
|
getAccessToken,
|
|
374
508
|
getTokens: (userId) => tokenStore.loadTokens(userId),
|
|
375
509
|
refreshTokens,
|
|
510
|
+
refreshIfExpiring,
|
|
376
511
|
disconnect: (userId) => tokenStore.clearTokens(userId),
|
|
377
512
|
listConnections,
|
|
378
513
|
listActions,
|
package/src/server/oauth.ts
CHANGED
|
@@ -85,6 +85,22 @@ export function tenancyHeaders(accessToken: string): Record<string, string> {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** When a refresh token stops working, in epoch milliseconds, from its
|
|
89
|
+
* `exp` claim. Null when the token carries no readable expiry, in which
|
|
90
|
+
* case only One can say whether it still works. */
|
|
91
|
+
export function refreshTokenExpiresAt(refreshToken: string): number | null {
|
|
92
|
+
try {
|
|
93
|
+
const payload = JSON.parse(
|
|
94
|
+
Buffer.from(refreshToken.split(".")[1], "base64url").toString(),
|
|
95
|
+
) as { exp?: unknown };
|
|
96
|
+
return typeof payload.exp === "number" && Number.isFinite(payload.exp)
|
|
97
|
+
? payload.exp * 1000
|
|
98
|
+
: null;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
/** The scopes the token was granted, from its claims. Display only. */
|
|
89
105
|
export function tokenScopes(accessToken: string): string[] {
|
|
90
106
|
try {
|
package/src/server/types.ts
CHANGED
|
@@ -13,13 +13,45 @@ export interface OneConnectTokens {
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Where the app keeps each user's tokens: its database, a cache, an
|
|
16
|
-
* encrypted cookie. The SDK never sees a token outside these
|
|
17
|
-
*
|
|
16
|
+
* encrypted cookie. The SDK never sees a token outside these calls.
|
|
17
|
+
* `userId` is the app's own id for its user.
|
|
18
18
|
*/
|
|
19
19
|
export interface OneConnectTokenStore {
|
|
20
20
|
saveTokens: (userId: string, tokens: OneConnectTokens) => Promise<void>;
|
|
21
21
|
loadTokens: (userId: string) => Promise<OneConnectTokens | null>;
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Deletes the user's tokens.
|
|
24
|
+
*
|
|
25
|
+
* `failed` is set when the SDK clears because One declared that pair
|
|
26
|
+
* dead. Delete only when the stored refresh token is still
|
|
27
|
+
* `failed.refreshToken`: a newer pair saved in the meantime (a
|
|
28
|
+
* reconnect, another server's refresh) must survive. `failed` is
|
|
29
|
+
* undefined for `disconnect`, which always deletes.
|
|
30
|
+
*/
|
|
31
|
+
clearTokens: (userId: string, failed?: OneConnectTokens) => Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Runs `run` while holding a lock on this user that every server and
|
|
34
|
+
* worker of the app shares: a Postgres advisory lock, a Redis lock, a
|
|
35
|
+
* row lock. The SDK loads, refreshes and saves the user's tokens
|
|
36
|
+
* inside it.
|
|
37
|
+
*
|
|
38
|
+
* Required when the app runs more than one process (serverless,
|
|
39
|
+
* several instances, a background worker). One rotates the refresh
|
|
40
|
+
* token on every use and treats a second use of the old one as theft,
|
|
41
|
+
* revoking the whole grant, so two processes refreshing the same user
|
|
42
|
+
* at once disconnect that user. Without a lock the SDK can only stop
|
|
43
|
+
* that inside a single process.
|
|
44
|
+
*
|
|
45
|
+
* Hold it for at least 60 seconds before any timeout: it spans one
|
|
46
|
+
* call to One's token endpoint.
|
|
47
|
+
*/
|
|
48
|
+
withLock?: <T>(userId: string, run: () => Promise<T>) => Promise<T>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RefreshIfExpiringOptions {
|
|
52
|
+
/** Refresh when the access token or the refresh token expires within
|
|
53
|
+
* this many milliseconds. One minute when omitted. */
|
|
54
|
+
withinMs?: number;
|
|
23
55
|
}
|
|
24
56
|
|
|
25
57
|
export interface OneConnectServerConfig {
|
|
@@ -137,6 +169,13 @@ export interface RunActionResult {
|
|
|
137
169
|
data: unknown;
|
|
138
170
|
}
|
|
139
171
|
|
|
172
|
+
/**
|
|
173
|
+
* - `not_connected`: no tokens are stored for this user.
|
|
174
|
+
* - `refresh_failed`: One declared the grant dead (revoked, expired or
|
|
175
|
+
* reused). The tokens were cleared; ask the user to connect again.
|
|
176
|
+
* - `request_failed`: One answered with an error or could not be
|
|
177
|
+
* reached. During a refresh the tokens are kept, so retry later.
|
|
178
|
+
*/
|
|
140
179
|
export type OneConnectErrorCode =
|
|
141
180
|
| "not_connected"
|
|
142
181
|
| "refresh_failed"
|
package/src/types.ts
CHANGED
|
@@ -42,8 +42,7 @@ export interface OneConnectReturn {
|
|
|
42
42
|
* object to override either.
|
|
43
43
|
*/
|
|
44
44
|
export type ConnectButtonPlatformInput =
|
|
45
|
-
| string
|
|
46
|
-
| { slug?: string; name?: string; imageUrl?: string };
|
|
45
|
+
string | { slug?: string; name?: string; imageUrl?: string };
|
|
47
46
|
|
|
48
47
|
/** A normalized chip: what the button actually draws. */
|
|
49
48
|
export interface ConnectButtonPlatform {
|
|
@@ -66,11 +65,9 @@ export interface ConnectButtonOptions {
|
|
|
66
65
|
variant?: ConnectButtonVariant;
|
|
67
66
|
/** Matches the host page, not One's page (that is connect.appTheme). */
|
|
68
67
|
theme?: OneConnectTheme;
|
|
69
|
-
/** Connector chips. The first three render; the rest fold into
|
|
70
|
-
*
|
|
68
|
+
/** Connector chips. The first three render; the rest fold into a "+N"
|
|
69
|
+
* chip, so that count only ever describes this list. */
|
|
71
70
|
platforms?: ConnectButtonPlatformInput[];
|
|
72
|
-
/** Extra count for the "+N" chip, e.g. 274 for "the whole catalog". */
|
|
73
|
-
moreCount?: number;
|
|
74
71
|
/** Sub-line on the block variant, shown while idle. */
|
|
75
72
|
description?: string;
|
|
76
73
|
/** Fill of the accent variant; One's lime when omitted. */
|
package/src/vue.ts
CHANGED
|
@@ -44,7 +44,6 @@ export const ConnectButton = defineComponent({
|
|
|
44
44
|
type: Array as PropType<ConnectButtonPlatformInput[]>,
|
|
45
45
|
default: undefined,
|
|
46
46
|
},
|
|
47
|
-
moreCount: { type: Number, default: undefined },
|
|
48
47
|
description: { type: String, default: undefined },
|
|
49
48
|
accentColor: { type: String, default: undefined },
|
|
50
49
|
connectedLabel: { type: String, default: undefined },
|
package/src/wrapper-options.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface ConnectButtonProps {
|
|
|
21
21
|
theme?: OneConnectTheme;
|
|
22
22
|
/** Connector slugs, or objects to override name or logo. */
|
|
23
23
|
platforms?: ConnectButtonPlatformInput[];
|
|
24
|
-
moreCount?: number;
|
|
25
24
|
description?: string;
|
|
26
25
|
accentColor?: string;
|
|
27
26
|
connectedLabel?: string;
|
|
@@ -47,7 +46,6 @@ export function optionsFromProps(
|
|
|
47
46
|
variant: props.variant,
|
|
48
47
|
theme: props.theme,
|
|
49
48
|
platforms: props.platforms,
|
|
50
|
-
moreCount: props.moreCount,
|
|
51
49
|
description: props.description,
|
|
52
50
|
accentColor: props.accentColor,
|
|
53
51
|
connectedLabel: props.connectedLabel,
|
|
@@ -63,7 +61,6 @@ export function propsIdentity(props: ConnectButtonProps): string {
|
|
|
63
61
|
props.variant,
|
|
64
62
|
props.theme,
|
|
65
63
|
props.platforms ?? [],
|
|
66
|
-
props.moreCount,
|
|
67
64
|
props.description,
|
|
68
65
|
props.accentColor,
|
|
69
66
|
props.connectedLabel,
|