@37signals/basecamp 0.12.0 → 0.14.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 +114 -17
- package/dist/.build-complete +0 -0
- package/dist/client.d.ts +53 -11
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +132 -150
- package/dist/client.js.map +1 -1
- package/dist/download.d.ts +7 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/download.js +82 -22
- package/dist/download.js.map +1 -1
- package/dist/errors.d.ts +19 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +138 -14
- package/dist/errors.js.map +1 -1
- package/dist/generated/metadata.d.ts.map +1 -1
- package/dist/generated/metadata.js +291 -141
- package/dist/generated/metadata.js.map +1 -1
- package/dist/generated/metadata.ts +291 -141
- package/dist/generated/openapi-stripped.json +5954 -3523
- package/dist/generated/path-mapping.d.ts.map +1 -1
- package/dist/generated/path-mapping.js +28 -16
- package/dist/generated/path-mapping.js.map +1 -1
- package/dist/generated/path-mapping.ts +28 -16
- package/dist/generated/schema.d.ts +4390 -2357
- package/dist/generated/services/bookmarks.d.ts +1 -1
- package/dist/generated/services/bookmarks.d.ts.map +1 -1
- package/dist/generated/services/bookmarks.ts +1 -1
- package/dist/generated/services/boosts.d.ts +10 -0
- package/dist/generated/services/boosts.d.ts.map +1 -1
- package/dist/generated/services/boosts.js +8 -0
- package/dist/generated/services/boosts.js.map +1 -1
- package/dist/generated/services/boosts.ts +12 -0
- package/dist/generated/services/campfires.d.ts +54 -40
- package/dist/generated/services/campfires.d.ts.map +1 -1
- package/dist/generated/services/campfires.js +82 -65
- package/dist/generated/services/campfires.js.map +1 -1
- package/dist/generated/services/campfires.ts +107 -85
- package/dist/generated/services/cards.d.ts +5 -0
- package/dist/generated/services/cards.d.ts.map +1 -1
- package/dist/generated/services/cards.js +4 -0
- package/dist/generated/services/cards.js.map +1 -1
- package/dist/generated/services/cards.ts +6 -0
- package/dist/generated/services/checkins.d.ts +14 -0
- package/dist/generated/services/checkins.d.ts.map +1 -1
- package/dist/generated/services/checkins.js +14 -1
- package/dist/generated/services/checkins.js.map +1 -1
- package/dist/generated/services/checkins.ts +20 -0
- package/dist/generated/services/client-approvals.d.ts +6 -3
- package/dist/generated/services/client-approvals.d.ts.map +1 -1
- package/dist/generated/services/client-approvals.js +8 -5
- package/dist/generated/services/client-approvals.js.map +1 -1
- package/dist/generated/services/client-approvals.ts +10 -5
- package/dist/generated/services/client-correspondences.d.ts +6 -3
- package/dist/generated/services/client-correspondences.d.ts.map +1 -1
- package/dist/generated/services/client-correspondences.js +8 -5
- package/dist/generated/services/client-correspondences.js.map +1 -1
- package/dist/generated/services/client-correspondences.ts +10 -5
- package/dist/generated/services/client-replies.d.ts +11 -4
- package/dist/generated/services/client-replies.d.ts.map +1 -1
- package/dist/generated/services/client-replies.js +16 -8
- package/dist/generated/services/client-replies.js.map +1 -1
- package/dist/generated/services/client-replies.ts +18 -8
- package/dist/generated/services/cloud-files.d.ts +92 -0
- package/dist/generated/services/cloud-files.d.ts.map +1 -0
- package/dist/generated/services/cloud-files.js +121 -0
- package/dist/generated/services/cloud-files.js.map +1 -0
- package/dist/generated/services/cloud-files.ts +188 -0
- package/dist/generated/services/comments.d.ts +5 -0
- package/dist/generated/services/comments.d.ts.map +1 -1
- package/dist/generated/services/comments.js +4 -0
- package/dist/generated/services/comments.js.map +1 -1
- package/dist/generated/services/comments.ts +6 -0
- package/dist/generated/services/documents.d.ts +12 -7
- package/dist/generated/services/documents.d.ts.map +1 -1
- package/dist/generated/services/documents.js +10 -6
- package/dist/generated/services/documents.js.map +1 -1
- package/dist/generated/services/documents.ts +14 -8
- package/dist/generated/services/drafts.d.ts +1 -1
- package/dist/generated/services/drafts.d.ts.map +1 -1
- package/dist/generated/services/drafts.ts +1 -1
- package/dist/generated/services/events.d.ts +5 -0
- package/dist/generated/services/events.d.ts.map +1 -1
- package/dist/generated/services/events.js +4 -0
- package/dist/generated/services/events.js.map +1 -1
- package/dist/generated/services/events.ts +6 -0
- package/dist/generated/services/everything.d.ts +14 -14
- package/dist/generated/services/everything.d.ts.map +1 -1
- package/dist/generated/services/everything.ts +14 -14
- package/dist/generated/services/folders.d.ts +96 -0
- package/dist/generated/services/folders.d.ts.map +1 -0
- package/dist/generated/services/folders.js +140 -0
- package/dist/generated/services/folders.js.map +1 -0
- package/dist/generated/services/folders.ts +201 -0
- package/dist/generated/services/forwards.d.ts +7 -20
- package/dist/generated/services/forwards.d.ts.map +1 -1
- package/dist/generated/services/forwards.js +6 -35
- package/dist/generated/services/forwards.js.map +1 -1
- package/dist/generated/services/forwards.ts +10 -48
- package/dist/generated/services/gauges.d.ts +7 -0
- package/dist/generated/services/gauges.d.ts.map +1 -1
- package/dist/generated/services/gauges.js +5 -1
- package/dist/generated/services/gauges.js.map +1 -1
- package/dist/generated/services/gauges.ts +9 -1
- package/dist/generated/services/google-documents.d.ts +98 -0
- package/dist/generated/services/google-documents.d.ts.map +1 -0
- package/dist/generated/services/google-documents.js +123 -0
- package/dist/generated/services/google-documents.js.map +1 -0
- package/dist/generated/services/google-documents.ts +196 -0
- package/dist/generated/services/index.d.ts +7 -4
- package/dist/generated/services/index.d.ts.map +1 -1
- package/dist/generated/services/index.js +7 -4
- package/dist/generated/services/index.js.map +1 -1
- package/dist/generated/services/index.ts +7 -4
- package/dist/generated/services/messages.d.ts +2 -0
- package/dist/generated/services/messages.d.ts.map +1 -1
- package/dist/generated/services/messages.js +1 -1
- package/dist/generated/services/messages.js.map +1 -1
- package/dist/generated/services/messages.ts +3 -1
- package/dist/generated/services/my-notes.d.ts +1 -1
- package/dist/generated/services/my-notes.js +1 -1
- package/dist/generated/services/my-notes.ts +1 -1
- package/dist/generated/services/my-notifications.d.ts +4 -2
- package/dist/generated/services/my-notifications.d.ts.map +1 -1
- package/dist/generated/services/my-notifications.js.map +1 -1
- package/dist/generated/services/my-notifications.ts +4 -2
- package/dist/generated/services/people.d.ts +11 -1
- package/dist/generated/services/people.d.ts.map +1 -1
- package/dist/generated/services/people.js +13 -2
- package/dist/generated/services/people.js.map +1 -1
- package/dist/generated/services/people.ts +15 -1
- package/dist/generated/services/projects.d.ts +26 -0
- package/dist/generated/services/projects.d.ts.map +1 -1
- package/dist/generated/services/projects.js +49 -1
- package/dist/generated/services/projects.js.map +1 -1
- package/dist/generated/services/projects.ts +61 -1
- package/dist/generated/services/recordings.d.ts +2 -12
- package/dist/generated/services/recordings.d.ts.map +1 -1
- package/dist/generated/services/recordings.js +1 -26
- package/dist/generated/services/recordings.js.map +1 -1
- package/dist/generated/services/recordings.ts +3 -31
- package/dist/generated/services/reports.d.ts +8 -12
- package/dist/generated/services/reports.d.ts.map +1 -1
- package/dist/generated/services/reports.js +11 -5
- package/dist/generated/services/reports.js.map +1 -1
- package/dist/generated/services/reports.ts +13 -14
- package/dist/generated/services/schedules.d.ts +94 -15
- package/dist/generated/services/schedules.d.ts.map +1 -1
- package/dist/generated/services/schedules.js +19 -8
- package/dist/generated/services/schedules.js.map +1 -1
- package/dist/generated/services/schedules.ts +107 -17
- package/dist/generated/services/search.d.ts +2 -0
- package/dist/generated/services/search.d.ts.map +1 -1
- package/dist/generated/services/search.js +1 -1
- package/dist/generated/services/search.js.map +1 -1
- package/dist/generated/services/search.ts +3 -1
- package/dist/generated/services/templates.d.ts +2 -0
- package/dist/generated/services/templates.d.ts.map +1 -1
- package/dist/generated/services/templates.js +1 -1
- package/dist/generated/services/templates.js.map +1 -1
- package/dist/generated/services/templates.ts +3 -1
- package/dist/generated/services/timeline.d.ts +2 -0
- package/dist/generated/services/timeline.d.ts.map +1 -1
- package/dist/generated/services/timeline.js +1 -0
- package/dist/generated/services/timeline.js.map +1 -1
- package/dist/generated/services/timeline.ts +3 -0
- package/dist/generated/services/timesheets.d.ts +16 -0
- package/dist/generated/services/timesheets.d.ts.map +1 -1
- package/dist/generated/services/timesheets.js +26 -2
- package/dist/generated/services/timesheets.js.map +1 -1
- package/dist/generated/services/timesheets.ts +35 -2
- package/dist/generated/services/todolist-groups.d.ts +11 -6
- package/dist/generated/services/todolist-groups.d.ts.map +1 -1
- package/dist/generated/services/todolist-groups.js +7 -3
- package/dist/generated/services/todolist-groups.js.map +1 -1
- package/dist/generated/services/todolist-groups.ts +13 -7
- package/dist/generated/services/todolists.d.ts +15 -13
- package/dist/generated/services/todolists.d.ts.map +1 -1
- package/dist/generated/services/todolists.js +11 -8
- package/dist/generated/services/todolists.js.map +1 -1
- package/dist/generated/services/todolists.ts +19 -14
- package/dist/generated/services/todos.d.ts +2 -12
- package/dist/generated/services/todos.d.ts.map +1 -1
- package/dist/generated/services/todos.js +1 -25
- package/dist/generated/services/todos.js.map +1 -1
- package/dist/generated/services/todos.ts +3 -30
- package/dist/generated/services/uploads.d.ts +41 -2
- package/dist/generated/services/uploads.d.ts.map +1 -1
- package/dist/generated/services/uploads.js +41 -1
- package/dist/generated/services/uploads.js.map +1 -1
- package/dist/generated/services/uploads.ts +71 -2
- package/dist/generated/services/vaults.d.ts +5 -0
- package/dist/generated/services/vaults.d.ts.map +1 -1
- package/dist/generated/services/vaults.js +4 -0
- package/dist/generated/services/vaults.js.map +1 -1
- package/dist/generated/services/vaults.ts +6 -0
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -8
- package/dist/index.js.map +1 -1
- package/dist/oauth/authorization-document.d.ts +201 -0
- package/dist/oauth/authorization-document.d.ts.map +1 -0
- package/dist/oauth/authorization-document.js +130 -0
- package/dist/oauth/authorization-document.js.map +1 -0
- package/dist/oauth/identity.d.ts +7 -1
- package/dist/oauth/identity.d.ts.map +1 -1
- package/dist/oauth/identity.js +8 -19
- package/dist/oauth/identity.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/index.js.map +1 -1
- package/dist/pagination-utils.d.ts +46 -0
- package/dist/pagination-utils.d.ts.map +1 -1
- package/dist/pagination-utils.js +85 -2
- package/dist/pagination-utils.js.map +1 -1
- package/dist/pagination.d.ts +11 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js.map +1 -1
- package/dist/retry.d.ts +110 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +253 -0
- package/dist/retry.js.map +1 -0
- package/dist/services/authorization.d.ts +22 -52
- package/dist/services/authorization.d.ts.map +1 -1
- package/dist/services/authorization.js +14 -32
- package/dist/services/authorization.js.map +1 -1
- package/dist/services/base.d.ts.map +1 -1
- package/dist/services/base.js +59 -5
- package/dist/services/base.js.map +1 -1
- package/dist/services/cards-extensions.d.ts +26 -21
- package/dist/services/cards-extensions.d.ts.map +1 -1
- package/dist/services/cards-extensions.js +28 -31
- package/dist/services/cards-extensions.js.map +1 -1
- package/dist/services/documents-extensions.d.ts +122 -0
- package/dist/services/documents-extensions.d.ts.map +1 -0
- package/dist/services/documents-extensions.js +125 -0
- package/dist/services/documents-extensions.js.map +1 -0
- package/dist/services/merge-safe.d.ts +167 -0
- package/dist/services/merge-safe.d.ts.map +1 -0
- package/dist/services/merge-safe.js +217 -0
- package/dist/services/merge-safe.js.map +1 -0
- package/dist/services/schedules-extensions.d.ts +266 -0
- package/dist/services/schedules-extensions.d.ts.map +1 -0
- package/dist/services/schedules-extensions.js +268 -0
- package/dist/services/schedules-extensions.js.map +1 -0
- package/dist/services/todolists-extensions.d.ts +110 -0
- package/dist/services/todolists-extensions.d.ts.map +1 -0
- package/dist/services/todolists-extensions.js +262 -0
- package/dist/services/todolists-extensions.js.map +1 -0
- package/dist/services/todos-extensions.d.ts +12 -1
- package/dist/services/todos-extensions.d.ts.map +1 -1
- package/dist/services/todos-extensions.js +27 -8
- package/dist/services/todos-extensions.js.map +1 -1
- package/package.json +6 -5
- package/src/generated/metadata.ts +291 -141
- package/src/generated/openapi-stripped.json +5954 -3523
- package/src/generated/path-mapping.ts +28 -16
- package/src/generated/schema.d.ts +4390 -2357
- package/src/generated/services/bookmarks.ts +1 -1
- package/src/generated/services/boosts.ts +12 -0
- package/src/generated/services/campfires.ts +107 -85
- package/src/generated/services/cards.ts +6 -0
- package/src/generated/services/checkins.ts +20 -0
- package/src/generated/services/client-approvals.ts +10 -5
- package/src/generated/services/client-correspondences.ts +10 -5
- package/src/generated/services/client-replies.ts +18 -8
- package/src/generated/services/cloud-files.ts +188 -0
- package/src/generated/services/comments.ts +6 -0
- package/src/generated/services/documents.ts +14 -8
- package/src/generated/services/drafts.ts +1 -1
- package/src/generated/services/events.ts +6 -0
- package/src/generated/services/everything.ts +14 -14
- package/src/generated/services/folders.ts +201 -0
- package/src/generated/services/forwards.ts +10 -48
- package/src/generated/services/gauges.ts +9 -1
- package/src/generated/services/google-documents.ts +196 -0
- package/src/generated/services/index.ts +7 -4
- package/src/generated/services/messages.ts +3 -1
- package/src/generated/services/my-notes.ts +1 -1
- package/src/generated/services/my-notifications.ts +4 -2
- package/src/generated/services/people.ts +15 -1
- package/src/generated/services/projects.ts +61 -1
- package/src/generated/services/recordings.ts +3 -31
- package/src/generated/services/reports.ts +13 -14
- package/src/generated/services/schedules.ts +107 -17
- package/src/generated/services/search.ts +3 -1
- package/src/generated/services/templates.ts +3 -1
- package/src/generated/services/timeline.ts +3 -0
- package/src/generated/services/timesheets.ts +35 -2
- package/src/generated/services/todolist-groups.ts +13 -7
- package/src/generated/services/todolists.ts +19 -14
- package/src/generated/services/todos.ts +3 -30
- package/src/generated/services/uploads.ts +71 -2
- package/src/generated/services/vaults.ts +6 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authorization document, as served by both issuers.
|
|
3
|
+
*
|
|
4
|
+
* Two different services fetch this document — {@link ../services/authorization.js | AuthorizationService}
|
|
5
|
+
* and {@link ./identity.js | discoverIdentity} — and they used to carry a copy of
|
|
6
|
+
* the mapping each. The copies had already drifted apart (`app_href` was required
|
|
7
|
+
* in one and optional in the other), which is exactly how a shape fix gets applied
|
|
8
|
+
* to one caller and missed on the other. The mapping lives here once.
|
|
9
|
+
*
|
|
10
|
+
* This module is a leaf on purpose: it imports nothing from the SDK, so either
|
|
11
|
+
* caller can take a *value* dependency on it without adding a runtime edge to the
|
|
12
|
+
* `base.ts` / `client.ts` cycle.
|
|
13
|
+
*
|
|
14
|
+
* ## Two issuers, two shapes
|
|
15
|
+
*
|
|
16
|
+
* Launchpad and a BC5 (bc3) issuer both serve `GET /authorization.json`, and the
|
|
17
|
+
* documents are not the same shape. bc3 renders its own from
|
|
18
|
+
* `app/views/api/authorizations/show.json.jbuilder`:
|
|
19
|
+
*
|
|
20
|
+
* | Field | Launchpad | BC5 |
|
|
21
|
+
* |---|---|---|
|
|
22
|
+
* | `identity.first_name` / `last_name` / `email_address` | present | absent |
|
|
23
|
+
* | `accounts[].product` / `app_href` | present | absent |
|
|
24
|
+
* | `accounts[].resource` | absent | present (RFC 8707 indicator) |
|
|
25
|
+
* | `scope` | absent | present, BC3-issued tokens only |
|
|
26
|
+
* | `expires_at` | ISO-8601 string | integer epoch **seconds** |
|
|
27
|
+
*
|
|
28
|
+
* Every field either issuer omits is typed optional here. The union is modelled
|
|
29
|
+
* rather than either shape alone, because a consumer reaches a BC5 issuer just by
|
|
30
|
+
* passing `endpoint:`, and BC5 issuers serving their own document is the intended
|
|
31
|
+
* end state.
|
|
32
|
+
*
|
|
33
|
+
* @see spec/api-gaps/bc5-authorization-document-shape.md
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* The authenticated user's identity.
|
|
37
|
+
*
|
|
38
|
+
* Only `id` is guaranteed. A BC5 issuer emits nothing but the identity id — it
|
|
39
|
+
* deliberately drops the PII that the API docs already say not to use for
|
|
40
|
+
* identifying users — so the name and email fields are absent there.
|
|
41
|
+
*/
|
|
42
|
+
export interface Identity {
|
|
43
|
+
/** User's unique identifier. Emitted by both issuers. */
|
|
44
|
+
id: number;
|
|
45
|
+
/** User's first name. Launchpad only — `undefined` from a BC5 issuer. */
|
|
46
|
+
firstName?: string;
|
|
47
|
+
/** User's last name. Launchpad only — `undefined` from a BC5 issuer. */
|
|
48
|
+
lastName?: string;
|
|
49
|
+
/** User's email address. Launchpad only — `undefined` from a BC5 issuer. */
|
|
50
|
+
emailAddress?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A Basecamp account the user has access to.
|
|
54
|
+
*/
|
|
55
|
+
export interface AuthorizedAccount {
|
|
56
|
+
/** Account's unique identifier. */
|
|
57
|
+
id: number;
|
|
58
|
+
/** Account name. */
|
|
59
|
+
name: string;
|
|
60
|
+
/**
|
|
61
|
+
* Product type (e.g. `"bc3"` for Basecamp, `"hey"` for HEY).
|
|
62
|
+
*
|
|
63
|
+
* Launchpad only. A BC5 issuer serves one product by construction and omits
|
|
64
|
+
* this, which is why {@link filterAccountsByProduct} treats a non-empty
|
|
65
|
+
* document with no `product` anywhere as one the filter cannot apply to.
|
|
66
|
+
*/
|
|
67
|
+
product?: string;
|
|
68
|
+
/** API URL for this account. Emitted by both issuers. */
|
|
69
|
+
href: string;
|
|
70
|
+
/** Web app URL for this account. Launchpad only. */
|
|
71
|
+
appHref?: string;
|
|
72
|
+
/**
|
|
73
|
+
* RFC 8707 resource indicator for this account (`urn:bc:account:<id>`).
|
|
74
|
+
*
|
|
75
|
+
* BC5 issuers only. Pass it as the `resource` parameter when requesting a
|
|
76
|
+
* token scoped to this account.
|
|
77
|
+
*/
|
|
78
|
+
resource?: string;
|
|
79
|
+
/** Whether the account is hidden from the user's view. */
|
|
80
|
+
hidden?: boolean;
|
|
81
|
+
/** Whether the account subscription has expired. */
|
|
82
|
+
expired?: boolean;
|
|
83
|
+
/** Whether this is the user's featured/primary account. */
|
|
84
|
+
featured?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Authorization information response.
|
|
88
|
+
*/
|
|
89
|
+
export interface AuthorizationInfo {
|
|
90
|
+
/**
|
|
91
|
+
* Token expiration timestamp.
|
|
92
|
+
*
|
|
93
|
+
* Parsed from either issuer's spelling — see {@link parseExpiresAt}.
|
|
94
|
+
*/
|
|
95
|
+
expiresAt: Date;
|
|
96
|
+
/** The authenticated user's identity. */
|
|
97
|
+
identity: Identity;
|
|
98
|
+
/** List of accounts the user can access. */
|
|
99
|
+
accounts: AuthorizedAccount[];
|
|
100
|
+
/**
|
|
101
|
+
* The token's granted scope.
|
|
102
|
+
*
|
|
103
|
+
* BC5 issuers only, and only for BC3-issued tokens — legacy Signal tokens
|
|
104
|
+
* predate scopes, so its absence is not an error.
|
|
105
|
+
*/
|
|
106
|
+
scope?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Whether a requested `product` filter was actually applied.
|
|
109
|
+
*
|
|
110
|
+
* Only meaningful when a product filter was requested. `false` means the
|
|
111
|
+
* document carried at least one account and no `product` on any of them — a
|
|
112
|
+
* BC5 document — so the filter was inapplicable and `accounts` is unfiltered
|
|
113
|
+
* rather than empty. An empty account list reports `true`: it is no evidence
|
|
114
|
+
* about the issuer either way. See {@link filterAccountsByProduct}.
|
|
115
|
+
*/
|
|
116
|
+
productFilterApplied?: boolean;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The raw wire document, covering both issuers.
|
|
120
|
+
*
|
|
121
|
+
* Every field that *either* issuer omits is optional, so this type describes what
|
|
122
|
+
* can actually arrive rather than what one issuer happens to send. `identity` and
|
|
123
|
+
* `accounts` are the two that stay required: both issuers emit both, so a document
|
|
124
|
+
* missing either is malformed, and throwing on it beats inventing an empty account
|
|
125
|
+
* list that a caller cannot distinguish from a real one.
|
|
126
|
+
*/
|
|
127
|
+
export interface RawAuthorizationDocument {
|
|
128
|
+
/** ISO-8601 string (Launchpad) or integer epoch seconds (BC5). */
|
|
129
|
+
expires_at?: string | number | null;
|
|
130
|
+
/** Both issuers always emit this, so it stays required — see the note below. */
|
|
131
|
+
identity: {
|
|
132
|
+
id: number;
|
|
133
|
+
first_name?: string;
|
|
134
|
+
last_name?: string;
|
|
135
|
+
email_address?: string;
|
|
136
|
+
};
|
|
137
|
+
/** Both issuers always emit this, so it stays required — see the note below. */
|
|
138
|
+
accounts: Array<{
|
|
139
|
+
id: number;
|
|
140
|
+
name: string;
|
|
141
|
+
product?: string;
|
|
142
|
+
href: string;
|
|
143
|
+
app_href?: string;
|
|
144
|
+
resource?: string;
|
|
145
|
+
hidden?: boolean;
|
|
146
|
+
expired?: boolean;
|
|
147
|
+
featured?: boolean;
|
|
148
|
+
}>;
|
|
149
|
+
scope?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parses `expires_at` from either issuer's spelling.
|
|
153
|
+
*
|
|
154
|
+
* Launchpad sends an ISO-8601 string; bc3 sends `@token.expires_at.to_i`, an
|
|
155
|
+
* integer epoch **seconds**. The distinction is the whole point of branching on
|
|
156
|
+
* `typeof`: `new Date(2085213356)` treats the number as *milliseconds* and yields
|
|
157
|
+
* a date in 1970 — a wrong answer rather than an exception, which then reads as
|
|
158
|
+
* an expired credential rather than a schema mismatch.
|
|
159
|
+
*
|
|
160
|
+
* bc3 renders `.to_i`, so a nil expiry arrives as `0`, never `null`. A `0`, a
|
|
161
|
+
* `null` and an absent field are all "no expiry known" and produce an Invalid
|
|
162
|
+
* Date, which is what a `Date`-typed field can say about the absence.
|
|
163
|
+
*/
|
|
164
|
+
export declare function parseExpiresAt(value: string | number | null | undefined): Date;
|
|
165
|
+
/**
|
|
166
|
+
* Filters accounts by product, or reports that it could not.
|
|
167
|
+
*
|
|
168
|
+
* A BC5 document carries no `product` on any account, so filtering it by product
|
|
169
|
+
* matches nothing. Returning `[]` there is silently wrong — the accounts exist,
|
|
170
|
+
* and the caller's next step is to pick a `href` out of a list the SDK just
|
|
171
|
+
* emptied. Instead: when *no* account in the document carries a `product`, the
|
|
172
|
+
* filter is inapplicable, and all accounts are returned with `applied: false` so
|
|
173
|
+
* a caller that cares can tell the two situations apart.
|
|
174
|
+
*
|
|
175
|
+
* When at least one account carries a `product`, the filter is meaningful and is
|
|
176
|
+
* applied normally — an empty result then genuinely means "no account matched".
|
|
177
|
+
*
|
|
178
|
+
* An **empty** account list is the edge that needs saying out loud. "No account
|
|
179
|
+
* carries a `product`" is vacuously true of `[]`, which would report the filter
|
|
180
|
+
* inapplicable — but Launchpad returns `accounts: []` for an identity with no
|
|
181
|
+
* currently accessible accounts, and that document is perfectly filterable. The
|
|
182
|
+
* result is the same list either way, so nothing observable changes; what changes
|
|
183
|
+
* is the claim the flag makes, and `applied: false` would assert "this issuer
|
|
184
|
+
* cannot filter by product" on no evidence at all. An empty list carries no
|
|
185
|
+
* evidence about the issuer, so treat the filter as applied: filtering nothing by
|
|
186
|
+
* anything yields nothing, which is exactly what happened.
|
|
187
|
+
*/
|
|
188
|
+
export declare function filterAccountsByProduct(accounts: AuthorizedAccount[], product: string): {
|
|
189
|
+
accounts: AuthorizedAccount[];
|
|
190
|
+
applied: boolean;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Maps a raw authorization document to {@link AuthorizationInfo}.
|
|
194
|
+
*
|
|
195
|
+
* Optionally applies a product filter; see {@link filterAccountsByProduct} for
|
|
196
|
+
* what happens when the document cannot be filtered by product.
|
|
197
|
+
*/
|
|
198
|
+
export declare function parseAuthorizationDocument(raw: RawAuthorizationDocument, options?: {
|
|
199
|
+
filterProduct?: string;
|
|
200
|
+
}): AuthorizationInfo;
|
|
201
|
+
//# sourceMappingURL=authorization-document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-document.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,SAAS,EAAE,IAAI,CAAC;IAChB,yCAAyC;IACzC,QAAQ,EAAE,QAAQ,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,gFAAgF;IAChF,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gFAAgF;IAChF,QAAQ,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAS9E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,OAAO,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAMrD;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,wBAAwB,EAC7B,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,iBAAiB,CAqCnB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The authorization document, as served by both issuers.
|
|
3
|
+
*
|
|
4
|
+
* Two different services fetch this document — {@link ../services/authorization.js | AuthorizationService}
|
|
5
|
+
* and {@link ./identity.js | discoverIdentity} — and they used to carry a copy of
|
|
6
|
+
* the mapping each. The copies had already drifted apart (`app_href` was required
|
|
7
|
+
* in one and optional in the other), which is exactly how a shape fix gets applied
|
|
8
|
+
* to one caller and missed on the other. The mapping lives here once.
|
|
9
|
+
*
|
|
10
|
+
* This module is a leaf on purpose: it imports nothing from the SDK, so either
|
|
11
|
+
* caller can take a *value* dependency on it without adding a runtime edge to the
|
|
12
|
+
* `base.ts` / `client.ts` cycle.
|
|
13
|
+
*
|
|
14
|
+
* ## Two issuers, two shapes
|
|
15
|
+
*
|
|
16
|
+
* Launchpad and a BC5 (bc3) issuer both serve `GET /authorization.json`, and the
|
|
17
|
+
* documents are not the same shape. bc3 renders its own from
|
|
18
|
+
* `app/views/api/authorizations/show.json.jbuilder`:
|
|
19
|
+
*
|
|
20
|
+
* | Field | Launchpad | BC5 |
|
|
21
|
+
* |---|---|---|
|
|
22
|
+
* | `identity.first_name` / `last_name` / `email_address` | present | absent |
|
|
23
|
+
* | `accounts[].product` / `app_href` | present | absent |
|
|
24
|
+
* | `accounts[].resource` | absent | present (RFC 8707 indicator) |
|
|
25
|
+
* | `scope` | absent | present, BC3-issued tokens only |
|
|
26
|
+
* | `expires_at` | ISO-8601 string | integer epoch **seconds** |
|
|
27
|
+
*
|
|
28
|
+
* Every field either issuer omits is typed optional here. The union is modelled
|
|
29
|
+
* rather than either shape alone, because a consumer reaches a BC5 issuer just by
|
|
30
|
+
* passing `endpoint:`, and BC5 issuers serving their own document is the intended
|
|
31
|
+
* end state.
|
|
32
|
+
*
|
|
33
|
+
* @see spec/api-gaps/bc5-authorization-document-shape.md
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Parses `expires_at` from either issuer's spelling.
|
|
37
|
+
*
|
|
38
|
+
* Launchpad sends an ISO-8601 string; bc3 sends `@token.expires_at.to_i`, an
|
|
39
|
+
* integer epoch **seconds**. The distinction is the whole point of branching on
|
|
40
|
+
* `typeof`: `new Date(2085213356)` treats the number as *milliseconds* and yields
|
|
41
|
+
* a date in 1970 — a wrong answer rather than an exception, which then reads as
|
|
42
|
+
* an expired credential rather than a schema mismatch.
|
|
43
|
+
*
|
|
44
|
+
* bc3 renders `.to_i`, so a nil expiry arrives as `0`, never `null`. A `0`, a
|
|
45
|
+
* `null` and an absent field are all "no expiry known" and produce an Invalid
|
|
46
|
+
* Date, which is what a `Date`-typed field can say about the absence.
|
|
47
|
+
*/
|
|
48
|
+
export function parseExpiresAt(value) {
|
|
49
|
+
if (typeof value === "number") {
|
|
50
|
+
// Epoch seconds, not milliseconds. `0` is bc3's rendering of a nil expiry.
|
|
51
|
+
return value === 0 ? new Date(NaN) : new Date(value * 1000);
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === "string" && value !== "") {
|
|
54
|
+
return new Date(value);
|
|
55
|
+
}
|
|
56
|
+
return new Date(NaN);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Filters accounts by product, or reports that it could not.
|
|
60
|
+
*
|
|
61
|
+
* A BC5 document carries no `product` on any account, so filtering it by product
|
|
62
|
+
* matches nothing. Returning `[]` there is silently wrong — the accounts exist,
|
|
63
|
+
* and the caller's next step is to pick a `href` out of a list the SDK just
|
|
64
|
+
* emptied. Instead: when *no* account in the document carries a `product`, the
|
|
65
|
+
* filter is inapplicable, and all accounts are returned with `applied: false` so
|
|
66
|
+
* a caller that cares can tell the two situations apart.
|
|
67
|
+
*
|
|
68
|
+
* When at least one account carries a `product`, the filter is meaningful and is
|
|
69
|
+
* applied normally — an empty result then genuinely means "no account matched".
|
|
70
|
+
*
|
|
71
|
+
* An **empty** account list is the edge that needs saying out loud. "No account
|
|
72
|
+
* carries a `product`" is vacuously true of `[]`, which would report the filter
|
|
73
|
+
* inapplicable — but Launchpad returns `accounts: []` for an identity with no
|
|
74
|
+
* currently accessible accounts, and that document is perfectly filterable. The
|
|
75
|
+
* result is the same list either way, so nothing observable changes; what changes
|
|
76
|
+
* is the claim the flag makes, and `applied: false` would assert "this issuer
|
|
77
|
+
* cannot filter by product" on no evidence at all. An empty list carries no
|
|
78
|
+
* evidence about the issuer, so treat the filter as applied: filtering nothing by
|
|
79
|
+
* anything yields nothing, which is exactly what happened.
|
|
80
|
+
*/
|
|
81
|
+
export function filterAccountsByProduct(accounts, product) {
|
|
82
|
+
const filterable = accounts.length === 0 || accounts.some((a) => a.product !== undefined);
|
|
83
|
+
if (!filterable) {
|
|
84
|
+
return { accounts, applied: false };
|
|
85
|
+
}
|
|
86
|
+
return { accounts: accounts.filter((a) => a.product === product), applied: true };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Maps a raw authorization document to {@link AuthorizationInfo}.
|
|
90
|
+
*
|
|
91
|
+
* Optionally applies a product filter; see {@link filterAccountsByProduct} for
|
|
92
|
+
* what happens when the document cannot be filtered by product.
|
|
93
|
+
*/
|
|
94
|
+
export function parseAuthorizationDocument(raw, options = {}) {
|
|
95
|
+
let accounts = raw.accounts.map((a) => ({
|
|
96
|
+
id: a.id,
|
|
97
|
+
name: a.name,
|
|
98
|
+
product: a.product,
|
|
99
|
+
href: a.href,
|
|
100
|
+
appHref: a.app_href,
|
|
101
|
+
resource: a.resource,
|
|
102
|
+
hidden: a.hidden,
|
|
103
|
+
expired: a.expired,
|
|
104
|
+
featured: a.featured,
|
|
105
|
+
}));
|
|
106
|
+
let productFilterApplied;
|
|
107
|
+
if (options.filterProduct !== undefined && options.filterProduct !== "") {
|
|
108
|
+
const filtered = filterAccountsByProduct(accounts, options.filterProduct);
|
|
109
|
+
accounts = filtered.accounts;
|
|
110
|
+
productFilterApplied = filtered.applied;
|
|
111
|
+
}
|
|
112
|
+
const info = {
|
|
113
|
+
expiresAt: parseExpiresAt(raw.expires_at),
|
|
114
|
+
identity: {
|
|
115
|
+
id: raw.identity.id,
|
|
116
|
+
firstName: raw.identity.first_name,
|
|
117
|
+
lastName: raw.identity.last_name,
|
|
118
|
+
emailAddress: raw.identity.email_address,
|
|
119
|
+
},
|
|
120
|
+
accounts,
|
|
121
|
+
};
|
|
122
|
+
if (raw.scope !== undefined) {
|
|
123
|
+
info.scope = raw.scope;
|
|
124
|
+
}
|
|
125
|
+
if (productFilterApplied !== undefined) {
|
|
126
|
+
info.productFilterApplied = productFilterApplied;
|
|
127
|
+
}
|
|
128
|
+
return info;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=authorization-document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-document.js","sourceRoot":"","sources":["../../src/oauth/authorization-document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AA0HH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyC;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,2EAA2E;QAC3E,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC9C,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,uBAAuB,CACrC,QAA6B,EAC7B,OAAe;IAEf,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC;IAC1F,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACpF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,GAA6B,EAC7B,UAAsC,EAAE;IAExC,IAAI,QAAQ,GAAwB,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,QAAQ;QACnB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;KACrB,CAAC,CAAC,CAAC;IAEJ,IAAI,oBAAyC,CAAC;IAC9C,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,IAAI,OAAO,CAAC,aAAa,KAAK,EAAE,EAAE,CAAC;QACxE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QAC1E,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAC7B,oBAAoB,GAAG,QAAQ,CAAC,OAAO,CAAC;IAC1C,CAAC;IAED,MAAM,IAAI,GAAsB;QAC9B,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;QACzC,QAAQ,EAAE;YACR,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE;YACnB,SAAS,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU;YAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS;YAChC,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,aAAa;SACzC;QACD,QAAQ;KACT,CAAC;IACF,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;IACzB,CAAC;IACD,IAAI,oBAAoB,KAAK,SAAS,EAAE,CAAC;QACvC,IAAI,CAAC,oBAAoB,GAAG,oBAAoB,CAAC;IACnD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/oauth/identity.d.ts
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Fetches authorization information (identity and accounts) from
|
|
5
5
|
* the Launchpad endpoint without requiring a full client instance.
|
|
6
|
+
*
|
|
7
|
+
* The endpoint is fixed to Launchpad here — unlike
|
|
8
|
+
* {@link ../services/authorization.js | AuthorizationService.getInfo}, which
|
|
9
|
+
* accepts an `endpoint:` override and so can be pointed at a BC5 issuer. Both
|
|
10
|
+
* share one mapping regardless; see `./authorization-document.js` for why the two
|
|
11
|
+
* documents differ and how the shared parser reconciles them.
|
|
6
12
|
*/
|
|
7
13
|
import type { TokenProvider } from "../client.js";
|
|
8
|
-
import type
|
|
14
|
+
import { type AuthorizationInfo } from "./authorization-document.js";
|
|
9
15
|
/**
|
|
10
16
|
* Fetches authorization information using an access token.
|
|
11
17
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,6BAA6B,CAAC;AAIrC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoC7F"}
|
package/dist/oauth/identity.js
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Fetches authorization information (identity and accounts) from
|
|
5
5
|
* the Launchpad endpoint without requiring a full client instance.
|
|
6
|
+
*
|
|
7
|
+
* The endpoint is fixed to Launchpad here — unlike
|
|
8
|
+
* {@link ../services/authorization.js | AuthorizationService.getInfo}, which
|
|
9
|
+
* accepts an `endpoint:` override and so can be pointed at a BC5 issuer. Both
|
|
10
|
+
* share one mapping regardless; see `./authorization-document.js` for why the two
|
|
11
|
+
* documents differ and how the shared parser reconciles them.
|
|
6
12
|
*/
|
|
7
13
|
import { BasecampError } from "../errors.js";
|
|
14
|
+
import { parseAuthorizationDocument, } from "./authorization-document.js";
|
|
8
15
|
const AUTHORIZATION_ENDPOINT = "https://launchpad.37signals.com/authorization.json";
|
|
9
16
|
/**
|
|
10
17
|
* Fetches authorization information using an access token.
|
|
@@ -59,24 +66,6 @@ export async function discoverIdentity(accessToken) {
|
|
|
59
66
|
catch {
|
|
60
67
|
throw new BasecampError("api_error", "Identity discovery returned invalid JSON");
|
|
61
68
|
}
|
|
62
|
-
return
|
|
63
|
-
expiresAt: new Date(raw.expires_at),
|
|
64
|
-
identity: {
|
|
65
|
-
id: raw.identity.id,
|
|
66
|
-
firstName: raw.identity.first_name,
|
|
67
|
-
lastName: raw.identity.last_name,
|
|
68
|
-
emailAddress: raw.identity.email_address,
|
|
69
|
-
},
|
|
70
|
-
accounts: raw.accounts.map((a) => ({
|
|
71
|
-
id: a.id,
|
|
72
|
-
name: a.name,
|
|
73
|
-
product: a.product,
|
|
74
|
-
href: a.href,
|
|
75
|
-
appHref: a.app_href ?? "",
|
|
76
|
-
hidden: a.hidden,
|
|
77
|
-
expired: a.expired,
|
|
78
|
-
featured: a.featured,
|
|
79
|
-
})),
|
|
80
|
-
};
|
|
69
|
+
return parseAuthorizationDocument(raw);
|
|
81
70
|
}
|
|
82
71
|
//# sourceMappingURL=identity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/oauth/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EACL,0BAA0B,GAG3B,MAAM,6BAA6B,CAAC;AAErC,MAAM,sBAAsB,GAAG,oDAAoD,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAA0B;IAC/D,MAAM,KAAK,GAAG,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IAEpF,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,sBAAsB,EAAE;YAC7C,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,MAAM,EAAE,kBAAkB;aAC3B;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,8BAA8B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACvH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,iCAAiC,EAAE;gBAC1E,UAAU,EAAE,GAAG;gBACf,IAAI,EAAE,2CAA2C;aAClD,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,mCAAmC,QAAQ,CAAC,MAAM,EAAE,EAAE;YACzF,UAAU,EAAE,QAAQ,CAAC,MAAM;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,GAA6B,CAAC;IAClC,IAAI,CAAC;QACH,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA6B,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,aAAa,CAAC,WAAW,EAAE,0CAA0C,CAAC,CAAC;IACnF,CAAC;IAED,OAAO,0BAA0B,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC"}
|
package/dist/oauth/index.d.ts
CHANGED
|
@@ -50,4 +50,5 @@ export { TokenManager, type TokenManagerOptions, } from "./token-manager.js";
|
|
|
50
50
|
export { startCallbackServer, type CallbackResult, type CallbackServerOptions, } from "./callback-server.js";
|
|
51
51
|
export { performInteractiveLogin, type InteractiveLoginOptions, } from "./interactive-login.js";
|
|
52
52
|
export { discoverIdentity, } from "./identity.js";
|
|
53
|
+
export { parseAuthorizationDocument, parseExpiresAt, filterAccountsByProduct, type RawAuthorizationDocument, } from "./authorization-document.js";
|
|
53
54
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,YAAY,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,6BAA6B,EAC7B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,EACtB,KAAK,gCAAgC,EACrC,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,QAAQ,EACR,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,2BAA2B,GACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,KAAK,IAAI,GACV,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAGH,YAAY,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,6BAA6B,EAC7B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,EACtB,KAAK,gCAAgC,EACrC,KAAK,qBAAqB,EAC1B,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,QAAQ,EACR,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,2BAA2B,GACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,KAAK,IAAI,GACV,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,uBAAuB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,6BAA6B,CAAC"}
|
package/dist/oauth/index.js
CHANGED
|
@@ -59,4 +59,6 @@ export { startCallbackServer, } from "./callback-server.js";
|
|
|
59
59
|
export { performInteractiveLogin, } from "./interactive-login.js";
|
|
60
60
|
// Identity
|
|
61
61
|
export { discoverIdentity, } from "./identity.js";
|
|
62
|
+
// The authorization document, shared by discoverIdentity and AuthorizationService
|
|
63
|
+
export { parseAuthorizationDocument, parseExpiresAt, filterAccountsByProduct, } from "./authorization-document.js";
|
|
62
64
|
//# sourceMappingURL=index.js.map
|
package/dist/oauth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAiBH,yBAAyB;AACzB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,GAIvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,eAAe,GAEhB,MAAM,oBAAoB,CAAC;AAE5B,YAAY;AACZ,OAAO,EACL,QAAQ,EACR,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,GAGnB,MAAM,gBAAgB,CAAC;AAExB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,GAEf,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,GAEd,MAAM,WAAW,CAAC;AAEnB,oBAAoB;AACpB,OAAO,EACL,qBAAqB,GAEtB,MAAM,gBAAgB,CAAC;AAExB,cAAc;AACd,OAAO,EACL,cAAc,GAEf,MAAM,kBAAkB,CAAC;AAE1B,gBAAgB;AAChB,OAAO,EACL,YAAY,GAEb,MAAM,oBAAoB,CAAC;AAE5B,kBAAkB;AAClB,OAAO,EACL,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAE9B,oBAAoB;AACpB,OAAO,EACL,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAEhC,WAAW;AACX,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAiBH,yBAAyB;AACzB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,GAIvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,eAAe,GAEhB,MAAM,oBAAoB,CAAC;AAE5B,YAAY;AACZ,OAAO,EACL,QAAQ,EACR,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,GAGnB,MAAM,gBAAgB,CAAC;AAExB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,GAEf,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,GAEd,MAAM,WAAW,CAAC;AAEnB,oBAAoB;AACpB,OAAO,EACL,qBAAqB,GAEtB,MAAM,gBAAgB,CAAC;AAExB,cAAc;AACd,OAAO,EACL,cAAc,GAEf,MAAM,kBAAkB,CAAC;AAE1B,gBAAgB;AAChB,OAAO,EACL,YAAY,GAEb,MAAM,oBAAoB,CAAC;AAE5B,kBAAkB;AAClB,OAAO,EACL,mBAAmB,GAGpB,MAAM,sBAAsB,CAAC;AAE9B,oBAAoB;AACpB,OAAO,EACL,uBAAuB,GAExB,MAAM,wBAAwB,CAAC;AAEhC,WAAW;AACX,OAAO,EACL,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAEvB,kFAAkF;AAClF,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,uBAAuB,GAExB,MAAM,6BAA6B,CAAC"}
|
|
@@ -5,6 +5,52 @@
|
|
|
5
5
|
* client.ts (which imports generated services) and base.ts (which
|
|
6
6
|
* generated services extend).
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Rejects any `maxPages` that would defeat or invert the cap.
|
|
10
|
+
*
|
|
11
|
+
* Lives here, beside `DEFAULT_MAX_PAGES` and for the same reason: `client.ts`,
|
|
12
|
+
* `services/base.ts` and this module's own helpers all need it, and this is the
|
|
13
|
+
* only one of them they can all import without a cycle. Three entry points set
|
|
14
|
+
* a cap — `createBasecampClient`, the standalone `fetchAllPages`/`paginateAll`,
|
|
15
|
+
* and `BaseService`'s constructor, which is exported and directly constructible
|
|
16
|
+
* — and a guard on two of the three is not a guard.
|
|
17
|
+
*
|
|
18
|
+
* `Number.isSafeInteger(n) && n > 0` is one predicate covering every value that
|
|
19
|
+
* breaks the bound, each of which passed silently while `maxPages` was
|
|
20
|
+
* unvalidated:
|
|
21
|
+
*
|
|
22
|
+
* - `Infinity` — `page === maxPages` is never true, so the loop is unbounded
|
|
23
|
+
* and the cap does nothing at all. Precisely the runaway it exists to stop.
|
|
24
|
+
* - Anything ABOVE `2 ** 53`, such as `Number.MAX_VALUE` — the same runaway
|
|
25
|
+
* through a different door, and the reason this is `isSafeInteger` rather
|
|
26
|
+
* than `isInteger`. `Number.isInteger(Number.MAX_VALUE)` is `true`, so the
|
|
27
|
+
* obvious predicate admits it. Past `2 ** 53` the counter stalls: `page++`
|
|
28
|
+
* on `2 ** 53` yields `2 ** 53` again, because the next integer is not
|
|
29
|
+
* representable, so a bound of `2 ** 53 + 2` is never reached. `2 ** 53`
|
|
30
|
+
* ITSELF is fine — the counter arrives there from `2 ** 53 - 1` and breaks
|
|
31
|
+
* — so rejecting it is deliberately conservative rather than necessary. A
|
|
32
|
+
* cap must be a number the counter can actually arrive at, and
|
|
33
|
+
* `MAX_SAFE_INTEGER` is the honest edge of that guarantee.
|
|
34
|
+
* - `2.5` — consumes 2 pages, then fetches a 3rd and discards it: a request to
|
|
35
|
+
* a URL taken from an attacker-influenceable header, whose response is never
|
|
36
|
+
* parsed or returned.
|
|
37
|
+
* - `0`, negative, `NaN` — consume ZERO pages, throwing away a response the
|
|
38
|
+
* caller already fetched and passed in.
|
|
39
|
+
*
|
|
40
|
+
* SPEC.md §2 step 5: "Validate `max_pages > 0`. → `⊥ BasecampError(code:
|
|
41
|
+
* "usage")` otherwise."
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertValidMaxPages(maxPages: number): void;
|
|
44
|
+
/**
|
|
45
|
+
* Default maximum pages to follow as a safety cap against infinite loops.
|
|
46
|
+
*
|
|
47
|
+
* Lives here rather than in base.ts because both base.ts and client.ts need it
|
|
48
|
+
* and this module is the one they can both import without a cycle. A malformed
|
|
49
|
+
* or hostile Link header can name the page it was served from, so every loop
|
|
50
|
+
* that follows rel="next" needs a bound that does not depend on the server
|
|
51
|
+
* ever stopping.
|
|
52
|
+
*/
|
|
53
|
+
export declare const DEFAULT_MAX_PAGES = 10000;
|
|
8
54
|
/**
|
|
9
55
|
* Parses the next URL from a Link header.
|
|
10
56
|
* Looks for rel="next" in the header value.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination-utils.d.ts","sourceRoot":"","sources":["../src/pagination-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"pagination-utils.d.ts","sourceRoot":"","sources":["../src/pagination-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6BH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQ1D;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAgBtE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAQ1D"}
|
package/dist/pagination-utils.js
CHANGED
|
@@ -5,6 +5,84 @@
|
|
|
5
5
|
* client.ts (which imports generated services) and base.ts (which
|
|
6
6
|
* generated services extend).
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extracts the contents of the first non-empty `<...>` pair.
|
|
10
|
+
*
|
|
11
|
+
* This is the leftmost-match semantics of `/<([^>]+)>/` in linear time. The
|
|
12
|
+
* regex form is quadratic on a header carrying many `<` with no reachable `>`,
|
|
13
|
+
* because every `<` is retried as a start position and each scans to the end.
|
|
14
|
+
* Searching for `>` from *after* the `<` visits each character once instead.
|
|
15
|
+
*
|
|
16
|
+
* An empty `<>` is skipped rather than returned, because `[^>]+` requires at
|
|
17
|
+
* least one character — the regex would move on to the next `<`, and so does
|
|
18
|
+
* this. Skipping costs nothing: the scan that found `<>` was O(1), so the loop
|
|
19
|
+
* only ever repeats after work it did not do.
|
|
20
|
+
*/
|
|
21
|
+
function extractAngleBracketed(part) {
|
|
22
|
+
let cursor = 0;
|
|
23
|
+
for (;;) {
|
|
24
|
+
const start = part.indexOf("<", cursor);
|
|
25
|
+
if (start < 0)
|
|
26
|
+
return null;
|
|
27
|
+
const end = part.indexOf(">", start + 1);
|
|
28
|
+
if (end < 0)
|
|
29
|
+
return null;
|
|
30
|
+
if (end > start + 1)
|
|
31
|
+
return part.slice(start + 1, end);
|
|
32
|
+
cursor = start + 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
import { BasecampError } from "./errors.js";
|
|
36
|
+
/**
|
|
37
|
+
* Rejects any `maxPages` that would defeat or invert the cap.
|
|
38
|
+
*
|
|
39
|
+
* Lives here, beside `DEFAULT_MAX_PAGES` and for the same reason: `client.ts`,
|
|
40
|
+
* `services/base.ts` and this module's own helpers all need it, and this is the
|
|
41
|
+
* only one of them they can all import without a cycle. Three entry points set
|
|
42
|
+
* a cap — `createBasecampClient`, the standalone `fetchAllPages`/`paginateAll`,
|
|
43
|
+
* and `BaseService`'s constructor, which is exported and directly constructible
|
|
44
|
+
* — and a guard on two of the three is not a guard.
|
|
45
|
+
*
|
|
46
|
+
* `Number.isSafeInteger(n) && n > 0` is one predicate covering every value that
|
|
47
|
+
* breaks the bound, each of which passed silently while `maxPages` was
|
|
48
|
+
* unvalidated:
|
|
49
|
+
*
|
|
50
|
+
* - `Infinity` — `page === maxPages` is never true, so the loop is unbounded
|
|
51
|
+
* and the cap does nothing at all. Precisely the runaway it exists to stop.
|
|
52
|
+
* - Anything ABOVE `2 ** 53`, such as `Number.MAX_VALUE` — the same runaway
|
|
53
|
+
* through a different door, and the reason this is `isSafeInteger` rather
|
|
54
|
+
* than `isInteger`. `Number.isInteger(Number.MAX_VALUE)` is `true`, so the
|
|
55
|
+
* obvious predicate admits it. Past `2 ** 53` the counter stalls: `page++`
|
|
56
|
+
* on `2 ** 53` yields `2 ** 53` again, because the next integer is not
|
|
57
|
+
* representable, so a bound of `2 ** 53 + 2` is never reached. `2 ** 53`
|
|
58
|
+
* ITSELF is fine — the counter arrives there from `2 ** 53 - 1` and breaks
|
|
59
|
+
* — so rejecting it is deliberately conservative rather than necessary. A
|
|
60
|
+
* cap must be a number the counter can actually arrive at, and
|
|
61
|
+
* `MAX_SAFE_INTEGER` is the honest edge of that guarantee.
|
|
62
|
+
* - `2.5` — consumes 2 pages, then fetches a 3rd and discards it: a request to
|
|
63
|
+
* a URL taken from an attacker-influenceable header, whose response is never
|
|
64
|
+
* parsed or returned.
|
|
65
|
+
* - `0`, negative, `NaN` — consume ZERO pages, throwing away a response the
|
|
66
|
+
* caller already fetched and passed in.
|
|
67
|
+
*
|
|
68
|
+
* SPEC.md §2 step 5: "Validate `max_pages > 0`. → `⊥ BasecampError(code:
|
|
69
|
+
* "usage")` otherwise."
|
|
70
|
+
*/
|
|
71
|
+
export function assertValidMaxPages(maxPages) {
|
|
72
|
+
if (!Number.isSafeInteger(maxPages) || maxPages <= 0) {
|
|
73
|
+
throw new BasecampError("usage", `maxPages must be a positive integer no larger than ${Number.MAX_SAFE_INTEGER}, got ${String(maxPages)}`, { hint: "Pass a whole number greater than 0, or omit maxPages to use the default cap." });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Default maximum pages to follow as a safety cap against infinite loops.
|
|
78
|
+
*
|
|
79
|
+
* Lives here rather than in base.ts because both base.ts and client.ts need it
|
|
80
|
+
* and this module is the one they can both import without a cycle. A malformed
|
|
81
|
+
* or hostile Link header can name the page it was served from, so every loop
|
|
82
|
+
* that follows rel="next" needs a bound that does not depend on the server
|
|
83
|
+
* ever stopping.
|
|
84
|
+
*/
|
|
85
|
+
export const DEFAULT_MAX_PAGES = 10_000;
|
|
8
86
|
/**
|
|
9
87
|
* Parses the next URL from a Link header.
|
|
10
88
|
* Looks for rel="next" in the header value.
|
|
@@ -18,8 +96,13 @@ export function parseNextLink(linkHeader) {
|
|
|
18
96
|
for (const part of linkHeader.split(",")) {
|
|
19
97
|
const trimmed = part.trim();
|
|
20
98
|
if (trimmed.includes('rel="next"')) {
|
|
21
|
-
|
|
22
|
-
|
|
99
|
+
// Keep scanning if this part is malformed. The previous code returned
|
|
100
|
+
// unconditionally here, so one unparseable `rel="next"` segment
|
|
101
|
+
// short-circuited the whole header to null; the other five SDKs fall
|
|
102
|
+
// through and keep looking.
|
|
103
|
+
const url = extractAngleBracketed(trimmed);
|
|
104
|
+
if (url !== null)
|
|
105
|
+
return url;
|
|
23
106
|
}
|
|
24
107
|
}
|
|
25
108
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination-utils.js","sourceRoot":"","sources":["../src/pagination-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,UAAyB;IACrD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACnC,MAAM,
|
|
1
|
+
{"version":3,"file":"pagination-utils.js","sourceRoot":"","sources":["../src/pagination-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;GAYG;AACH,SAAS,qBAAqB,CAAC,IAAY;IACzC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,SAAS,CAAC;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACxC,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACzC,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACzB,IAAI,GAAG,GAAG,KAAK,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QACvD,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,aAAa,CACrB,OAAO,EACP,sDAAsD,MAAM,CAAC,gBAAgB,SAAS,MAAM,CAAC,QAAQ,CAAC,EAAE,EACxG,EAAE,IAAI,EAAE,8EAA8E,EAAE,CACzF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,UAAyB;IACrD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACnC,sEAAsE;YACtE,gEAAgE;YAChE,qEAAqE;YACrE,4BAA4B;YAC5B,MAAM,GAAG,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,GAAG,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,MAAc;IACrD,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,CAAS;IAC/C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/pagination.d.ts
CHANGED
|
@@ -26,6 +26,17 @@ export interface PaginationOptions {
|
|
|
26
26
|
* When undefined or 0, all pages are fetched.
|
|
27
27
|
*/
|
|
28
28
|
maxItems?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Selects a single page. A positive `page` returns exactly that page in
|
|
31
|
+
* exactly one request: the `Link: rel="next"` header is not followed, and
|
|
32
|
+
* `.meta.truncated` reports whether a further page existed. When undefined,
|
|
33
|
+
* 0, or negative, auto-pagination walks the whole collection.
|
|
34
|
+
*
|
|
35
|
+
* Only meaningful for operations whose endpoint honors `?page=`; the handful
|
|
36
|
+
* of list endpoints that return their whole collection at once never emit a
|
|
37
|
+
* next link, so pinning a page there changes nothing. See SPEC section 8.
|
|
38
|
+
*/
|
|
39
|
+
page?: number;
|
|
29
40
|
}
|
|
30
41
|
/**
|
|
31
42
|
* An array of results with pagination metadata.
|
package/dist/pagination.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,UAAU,CAAC,CAAC,CAAE,SAAQ,KAAK,CAAC,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAExB;;;;;OAKG;IACH,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAE9C;gBAEW,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ;CAavC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAK1D"}
|