@withone/connect 0.17.0 → 0.17.2
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 +12 -3
- package/dist/server/index.cjs.js +33 -4
- package/dist/server/index.esm.js +33 -4
- package/dist/server/types.d.ts +20 -3
- package/package.json +1 -1
- package/skills/one-connect/SKILL.md +14 -4
- package/src/server/index.ts +22 -9
- package/src/server/types.ts +21 -3
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<a href="https://withone.ai/products/connect"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.withone.ai%2Fv1%2Favailable-connectors%3Flimit%3D1&query=%24.total&label=connectors" alt="Live count of connectors on One"></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
-
One Connect lets your users grant your app **scoped, revocable access to their own tools**: Gmail, Slack, Notion, Stripe and
|
|
24
|
+
One Connect lets your users grant your app **scoped, revocable access to their own tools**: Gmail, Slack, Notion, Stripe and the rest of 1,000+ platforms, with 130,000+ actions between them. The connections stay in your user's One account. Your app holds only what they granted, and One checks that on every call.
|
|
25
25
|
|
|
26
26
|
You add three things: a button, two backend routes, and the calls you make with the grant.
|
|
27
27
|
|
|
@@ -281,7 +281,7 @@ const reply = await oneConnect.runAction(userId, {
|
|
|
281
281
|
actionId: action._id,
|
|
282
282
|
body: { limit: 10 },
|
|
283
283
|
});
|
|
284
|
-
// { status, ok, blockedByGrant, data }
|
|
284
|
+
// { status, ok, blockedByGrant, refusal, data }
|
|
285
285
|
```
|
|
286
286
|
|
|
287
287
|
Each connection carries its `access`: `{ policy: "full" }`, `{ policy: "methods", methods }` or `{ policy: "actions", actions }`. Plan from it before you call.
|
|
@@ -306,7 +306,15 @@ You never set an auth header: the client adds the connect key and the user's id
|
|
|
306
306
|
|
|
307
307
|
## 6 · Errors
|
|
308
308
|
|
|
309
|
-
|
|
309
|
+
When One itself turns a call down, `runAction` says why in `refusal`:
|
|
310
|
+
|
|
311
|
+
| `refusal` | Status | What to do |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `"grant"` (`blockedByGrant: true`) | `403` | The call is outside what the user granted. The provider was never called; don't retry it. |
|
|
314
|
+
| `"plan_limit"` | `402` | A plan limit, not the grant. Wait and retry later; don't ask the user to reconnect. |
|
|
315
|
+
| `"rate_limit"` | `429` | Too many calls. Wait and retry. |
|
|
316
|
+
| `"one_error"` | other, e.g. `5xx` | Retry with a backoff. |
|
|
317
|
+
| `null` | any | Not One's refusal: the provider's own answer, or a success. |
|
|
310
318
|
|
|
311
319
|
Everything else is a `OneConnectError` with a `code`:
|
|
312
320
|
|
|
@@ -393,6 +401,7 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
|
|
|
393
401
|
| Every call fails with `request_failed`: "One did not accept the connect key" | The key is not this app's, or it was revoked. Create a key on the app's page and update `ONE_CONNECT_KEY`. |
|
|
394
402
|
| Every user's calls throw `reconnect_required` | The app is deactivated. Check that it is active in the dashboard. |
|
|
395
403
|
| A call returns `403` with `blockedByGrant: true` | The action is outside what the user granted. Ask for it in your permission set; users see new tools the next time they connect. |
|
|
404
|
+
| A call returns `402` with `refusal: "plan_limit"` | A plan limit was hit, not the grant. Wait and retry later; don't ask the user to reconnect. |
|
|
396
405
|
|
|
397
406
|
## Supported versions
|
|
398
407
|
|
package/dist/server/index.cjs.js
CHANGED
|
@@ -185,6 +185,18 @@ function tokenScopes(accessToken) {
|
|
|
185
185
|
* method and path the SDK runs it with.
|
|
186
186
|
*/
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Why One itself turned a call down, when it did. A provider's own error
|
|
190
|
+
* (Gmail's 404, Stripe's 400) is not One's refusal and reads `null`.
|
|
191
|
+
*
|
|
192
|
+
* - `grant`: the call is outside what the user granted (403). Do not retry.
|
|
193
|
+
* - `plan_limit`: a plan limit was hit (402, "Please try again later or
|
|
194
|
+
* upgrade your plan"). Not about the grant: wait and retry later.
|
|
195
|
+
* - `rate_limit`: too many calls (429). Wait and retry later.
|
|
196
|
+
* - `one_error`: any other error from One (for example a 5xx). Retry with
|
|
197
|
+
* a backoff.
|
|
198
|
+
*/
|
|
199
|
+
|
|
188
200
|
/**
|
|
189
201
|
* - `not_connected`: nothing is stored for this user.
|
|
190
202
|
* - `reconnect_required` (key mode): One will not act for this user.
|
|
@@ -510,6 +522,23 @@ function createTokenCredential(tokenStore, postToken) {
|
|
|
510
522
|
* The Next.js and Node adapters turn the first two into route handlers.
|
|
511
523
|
* See `./key` and `./token` for what each mode stores and sends.
|
|
512
524
|
*/
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Why One itself refused a call, from its reply. One's own errors carry a
|
|
528
|
+
* correlationId; a provider's error does not, so it reads `null`. Only a
|
|
529
|
+
* 403 is the grant: One also answers a plan limit (402), a rate limit
|
|
530
|
+
* (429) and its own failures (5xx) with a correlationId, and none of those
|
|
531
|
+
* mean the call is outside what the user granted.
|
|
532
|
+
*/
|
|
533
|
+
function oneRefusal(response, data) {
|
|
534
|
+
if (response.ok) return null;
|
|
535
|
+
const fromOne = typeof data === "object" && data !== null && "correlationId" in data;
|
|
536
|
+
if (!fromOne) return null;
|
|
537
|
+
if (response.status === 403) return "grant";
|
|
538
|
+
if (response.status === 402) return "plan_limit";
|
|
539
|
+
if (response.status === 429) return "rate_limit";
|
|
540
|
+
return "one_error";
|
|
541
|
+
}
|
|
513
542
|
const CATALOG_PAGE_SIZE = 100;
|
|
514
543
|
const CATALOG_MAX_PAGES = 20;
|
|
515
544
|
const SEARCH_DEFAULT_LIMIT = 5;
|
|
@@ -860,6 +889,7 @@ function createOneConnect(config) {
|
|
|
860
889
|
status: response.status,
|
|
861
890
|
ok: false,
|
|
862
891
|
blockedByGrant: true,
|
|
892
|
+
refusal: "grant",
|
|
863
893
|
data: text
|
|
864
894
|
};
|
|
865
895
|
throw refused;
|
|
@@ -873,13 +903,12 @@ function createOneConnect(config) {
|
|
|
873
903
|
} catch {
|
|
874
904
|
/* the provider answered with something other than JSON */
|
|
875
905
|
}
|
|
876
|
-
|
|
877
|
-
// not. A One-shaped 403 is the grant working, not the provider.
|
|
878
|
-
const blockedByGrant = !response.ok && typeof data === "object" && data !== null && "correlationId" in data;
|
|
906
|
+
const refusal = oneRefusal(response, data);
|
|
879
907
|
return {
|
|
880
908
|
status: response.status,
|
|
881
909
|
ok: response.ok,
|
|
882
|
-
blockedByGrant,
|
|
910
|
+
blockedByGrant: refusal === "grant",
|
|
911
|
+
refusal,
|
|
883
912
|
data
|
|
884
913
|
};
|
|
885
914
|
};
|
package/dist/server/index.esm.js
CHANGED
|
@@ -183,6 +183,18 @@ function tokenScopes(accessToken) {
|
|
|
183
183
|
* method and path the SDK runs it with.
|
|
184
184
|
*/
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Why One itself turned a call down, when it did. A provider's own error
|
|
188
|
+
* (Gmail's 404, Stripe's 400) is not One's refusal and reads `null`.
|
|
189
|
+
*
|
|
190
|
+
* - `grant`: the call is outside what the user granted (403). Do not retry.
|
|
191
|
+
* - `plan_limit`: a plan limit was hit (402, "Please try again later or
|
|
192
|
+
* upgrade your plan"). Not about the grant: wait and retry later.
|
|
193
|
+
* - `rate_limit`: too many calls (429). Wait and retry later.
|
|
194
|
+
* - `one_error`: any other error from One (for example a 5xx). Retry with
|
|
195
|
+
* a backoff.
|
|
196
|
+
*/
|
|
197
|
+
|
|
186
198
|
/**
|
|
187
199
|
* - `not_connected`: nothing is stored for this user.
|
|
188
200
|
* - `reconnect_required` (key mode): One will not act for this user.
|
|
@@ -508,6 +520,23 @@ function createTokenCredential(tokenStore, postToken) {
|
|
|
508
520
|
* The Next.js and Node adapters turn the first two into route handlers.
|
|
509
521
|
* See `./key` and `./token` for what each mode stores and sends.
|
|
510
522
|
*/
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Why One itself refused a call, from its reply. One's own errors carry a
|
|
526
|
+
* correlationId; a provider's error does not, so it reads `null`. Only a
|
|
527
|
+
* 403 is the grant: One also answers a plan limit (402), a rate limit
|
|
528
|
+
* (429) and its own failures (5xx) with a correlationId, and none of those
|
|
529
|
+
* mean the call is outside what the user granted.
|
|
530
|
+
*/
|
|
531
|
+
function oneRefusal(response, data) {
|
|
532
|
+
if (response.ok) return null;
|
|
533
|
+
const fromOne = typeof data === "object" && data !== null && "correlationId" in data;
|
|
534
|
+
if (!fromOne) return null;
|
|
535
|
+
if (response.status === 403) return "grant";
|
|
536
|
+
if (response.status === 402) return "plan_limit";
|
|
537
|
+
if (response.status === 429) return "rate_limit";
|
|
538
|
+
return "one_error";
|
|
539
|
+
}
|
|
511
540
|
const CATALOG_PAGE_SIZE = 100;
|
|
512
541
|
const CATALOG_MAX_PAGES = 20;
|
|
513
542
|
const SEARCH_DEFAULT_LIMIT = 5;
|
|
@@ -858,6 +887,7 @@ function createOneConnect(config) {
|
|
|
858
887
|
status: response.status,
|
|
859
888
|
ok: false,
|
|
860
889
|
blockedByGrant: true,
|
|
890
|
+
refusal: "grant",
|
|
861
891
|
data: text
|
|
862
892
|
};
|
|
863
893
|
throw refused;
|
|
@@ -871,13 +901,12 @@ function createOneConnect(config) {
|
|
|
871
901
|
} catch {
|
|
872
902
|
/* the provider answered with something other than JSON */
|
|
873
903
|
}
|
|
874
|
-
|
|
875
|
-
// not. A One-shaped 403 is the grant working, not the provider.
|
|
876
|
-
const blockedByGrant = !response.ok && typeof data === "object" && data !== null && "correlationId" in data;
|
|
904
|
+
const refusal = oneRefusal(response, data);
|
|
877
905
|
return {
|
|
878
906
|
status: response.status,
|
|
879
907
|
ok: response.ok,
|
|
880
|
-
blockedByGrant,
|
|
908
|
+
blockedByGrant: refusal === "grant",
|
|
909
|
+
refusal,
|
|
881
910
|
data
|
|
882
911
|
};
|
|
883
912
|
};
|
package/dist/server/types.d.ts
CHANGED
|
@@ -260,13 +260,30 @@ export interface RunActionInput {
|
|
|
260
260
|
* notation); "multipart" for file-style uploads. */
|
|
261
261
|
encoding?: ActionBodyEncoding;
|
|
262
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Why One itself turned a call down, when it did. A provider's own error
|
|
265
|
+
* (Gmail's 404, Stripe's 400) is not One's refusal and reads `null`.
|
|
266
|
+
*
|
|
267
|
+
* - `grant`: the call is outside what the user granted (403). Do not retry.
|
|
268
|
+
* - `plan_limit`: a plan limit was hit (402, "Please try again later or
|
|
269
|
+
* upgrade your plan"). Not about the grant: wait and retry later.
|
|
270
|
+
* - `rate_limit`: too many calls (429). Wait and retry later.
|
|
271
|
+
* - `one_error`: any other error from One (for example a 5xx). Retry with
|
|
272
|
+
* a backoff.
|
|
273
|
+
*/
|
|
274
|
+
export type OneRefusal = "grant" | "plan_limit" | "rate_limit" | "one_error";
|
|
263
275
|
export interface RunActionResult {
|
|
264
276
|
status: number;
|
|
265
277
|
ok: boolean;
|
|
266
|
-
/** True when One refused the call because it is outside the grant
|
|
267
|
-
* The provider was never called. Do not retry. Key mode sets
|
|
268
|
-
* token mode it stays false today, so treat any 403 there as
|
|
278
|
+
/** True only when One refused the call because it is outside the grant
|
|
279
|
+
* (a 403). The provider was never called. Do not retry. Key mode sets
|
|
280
|
+
* it; in token mode it stays false today, so treat any 403 there as
|
|
281
|
+
* refused. One's other refusals (plan limit, rate limit, its own
|
|
282
|
+
* errors) are not about the grant: see `refusal`. */
|
|
269
283
|
blockedByGrant: boolean;
|
|
284
|
+
/** Why One turned the call down, or `null` when it didn't (the call
|
|
285
|
+
* succeeded, or the provider answered with its own error). */
|
|
286
|
+
refusal: OneRefusal | null;
|
|
270
287
|
data: unknown;
|
|
271
288
|
}
|
|
272
289
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withone/connect",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: one-connect
|
|
3
|
-
description: Add One Connect to an application so its users can grant the app scoped, revocable access to their own One-connected tools (Gmail, Slack, Notion, Stripe and
|
|
3
|
+
description: Add One Connect to an application so its users can grant the app scoped, revocable access to their own One-connected tools (Gmail, Slack, Notion, Stripe and the rest of 1,000+ platforms). Use when wiring @withone/connect into an app - choosing key or token mode, the two backend routes, the Connect button or the app's own button, and calling One with the grant.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# One Connect
|
|
@@ -214,7 +214,7 @@ const reply = await oneConnect.runAction(userId, {
|
|
|
214
214
|
encoding: "json", // or "form" / "multipart", as the guide says
|
|
215
215
|
headers: {}, // only when the guide names one
|
|
216
216
|
});
|
|
217
|
-
// { status, ok, blockedByGrant, data }
|
|
217
|
+
// { status, ok, blockedByGrant, refusal, data }
|
|
218
218
|
```
|
|
219
219
|
|
|
220
220
|
Each connection's `access` is `{ policy: "full" }`, `{ policy: "methods",
|
|
@@ -230,8 +230,18 @@ user's id to every call it makes.
|
|
|
230
230
|
|
|
231
231
|
## 8 - Errors
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
`refusal` says why One itself turned a call down:
|
|
234
|
+
|
|
235
|
+
- `"grant"` (`403`, `blockedByGrant: true`): outside what the user granted.
|
|
236
|
+
Do not retry it.
|
|
237
|
+
- `"plan_limit"` (`402`): a plan limit, not the grant. Wait and retry later.
|
|
238
|
+
Do not ask the user to reconnect.
|
|
239
|
+
- `"rate_limit"` (`429`): too many calls. Wait and retry.
|
|
240
|
+
- `"one_error"` (other, e.g. `5xx`): retry with a backoff.
|
|
241
|
+
- `null`: not One's refusal; the provider's own answer, or a success.
|
|
242
|
+
|
|
243
|
+
Only a `403` means "outside the grant". Never treat a `402`, `429` or `5xx`
|
|
244
|
+
as a missing permission.
|
|
235
245
|
|
|
236
246
|
Errors are `OneConnectError` with a `code`. Handle them where the app calls One:
|
|
237
247
|
|
package/src/server/index.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
type ReachableConnection,
|
|
60
60
|
type RefreshIfExpiringOptions,
|
|
61
61
|
type RunActionInput,
|
|
62
|
+
type OneRefusal,
|
|
62
63
|
type RunActionResult,
|
|
63
64
|
type SearchActionsOptions,
|
|
64
65
|
type StartAuthorizationInput,
|
|
@@ -69,6 +70,24 @@ export * from "./types";
|
|
|
69
70
|
export { encodeUserReference, parseUserReference } from "./key";
|
|
70
71
|
export { refreshTokenExpiresAt, tenancyHeaders, tokenScopes } from "./oauth";
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Why One itself refused a call, from its reply. One's own errors carry a
|
|
75
|
+
* correlationId; a provider's error does not, so it reads `null`. Only a
|
|
76
|
+
* 403 is the grant: One also answers a plan limit (402), a rate limit
|
|
77
|
+
* (429) and its own failures (5xx) with a correlationId, and none of those
|
|
78
|
+
* mean the call is outside what the user granted.
|
|
79
|
+
*/
|
|
80
|
+
function oneRefusal(response: Response, data: unknown): OneRefusal | null {
|
|
81
|
+
if (response.ok) return null;
|
|
82
|
+
const fromOne =
|
|
83
|
+
typeof data === "object" && data !== null && "correlationId" in (data as Record<string, unknown>);
|
|
84
|
+
if (!fromOne) return null;
|
|
85
|
+
if (response.status === 403) return "grant";
|
|
86
|
+
if (response.status === 402) return "plan_limit";
|
|
87
|
+
if (response.status === 429) return "rate_limit";
|
|
88
|
+
return "one_error";
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
const CATALOG_PAGE_SIZE = 100;
|
|
73
92
|
const CATALOG_MAX_PAGES = 20;
|
|
74
93
|
const SEARCH_DEFAULT_LIMIT = 5;
|
|
@@ -624,7 +643,7 @@ export function createOneConnect(
|
|
|
624
643
|
// tells them apart: if that still answers, the consent stands
|
|
625
644
|
// and it was this call One refused.
|
|
626
645
|
if (await consentStands(userId, refused))
|
|
627
|
-
return { status: response.status, ok: false, blockedByGrant: true, data: text };
|
|
646
|
+
return { status: response.status, ok: false, blockedByGrant: true, refusal: "grant", data: text };
|
|
628
647
|
throw refused;
|
|
629
648
|
}
|
|
630
649
|
// One refusing the credential is not an answer to the action.
|
|
@@ -636,14 +655,8 @@ export function createOneConnect(
|
|
|
636
655
|
} catch {
|
|
637
656
|
/* the provider answered with something other than JSON */
|
|
638
657
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const blockedByGrant =
|
|
642
|
-
!response.ok &&
|
|
643
|
-
typeof data === "object" &&
|
|
644
|
-
data !== null &&
|
|
645
|
-
"correlationId" in (data as Record<string, unknown>);
|
|
646
|
-
return { status: response.status, ok: response.ok, blockedByGrant, data };
|
|
658
|
+
const refusal = oneRefusal(response, data);
|
|
659
|
+
return { status: response.status, ok: response.ok, blockedByGrant: refusal === "grant", refusal, data };
|
|
647
660
|
};
|
|
648
661
|
|
|
649
662
|
const shared = {
|
package/src/server/types.ts
CHANGED
|
@@ -279,13 +279,31 @@ export interface RunActionInput {
|
|
|
279
279
|
encoding?: ActionBodyEncoding;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Why One itself turned a call down, when it did. A provider's own error
|
|
284
|
+
* (Gmail's 404, Stripe's 400) is not One's refusal and reads `null`.
|
|
285
|
+
*
|
|
286
|
+
* - `grant`: the call is outside what the user granted (403). Do not retry.
|
|
287
|
+
* - `plan_limit`: a plan limit was hit (402, "Please try again later or
|
|
288
|
+
* upgrade your plan"). Not about the grant: wait and retry later.
|
|
289
|
+
* - `rate_limit`: too many calls (429). Wait and retry later.
|
|
290
|
+
* - `one_error`: any other error from One (for example a 5xx). Retry with
|
|
291
|
+
* a backoff.
|
|
292
|
+
*/
|
|
293
|
+
export type OneRefusal = "grant" | "plan_limit" | "rate_limit" | "one_error";
|
|
294
|
+
|
|
282
295
|
export interface RunActionResult {
|
|
283
296
|
status: number;
|
|
284
297
|
ok: boolean;
|
|
285
|
-
/** True when One refused the call because it is outside the grant
|
|
286
|
-
* The provider was never called. Do not retry. Key mode sets
|
|
287
|
-
* token mode it stays false today, so treat any 403 there as
|
|
298
|
+
/** True only when One refused the call because it is outside the grant
|
|
299
|
+
* (a 403). The provider was never called. Do not retry. Key mode sets
|
|
300
|
+
* it; in token mode it stays false today, so treat any 403 there as
|
|
301
|
+
* refused. One's other refusals (plan limit, rate limit, its own
|
|
302
|
+
* errors) are not about the grant: see `refusal`. */
|
|
288
303
|
blockedByGrant: boolean;
|
|
304
|
+
/** Why One turned the call down, or `null` when it didn't (the call
|
|
305
|
+
* succeeded, or the provider answered with its own error). */
|
|
306
|
+
refusal: OneRefusal | null;
|
|
289
307
|
data: unknown;
|
|
290
308
|
}
|
|
291
309
|
|