@fragno-dev/api-fragment 0.0.0-stage → 0.0.1
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/LICENSE.md +16 -0
- package/README.md +249 -2
- package/dist/browser/client/client-3ZV9sw3i.js +2580 -0
- package/dist/browser/client/client-3ZV9sw3i.js.map +1 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts +711 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts.map +1 -0
- package/dist/browser/client/client.d.ts +1645 -0
- package/dist/browser/client/client.d.ts.map +1 -0
- package/dist/browser/client/client.js +2 -0
- package/dist/browser/client/react.d.ts +1665 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +194 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +1668 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +129 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +1668 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +131 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +1695 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +159 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +1668 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +127 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-D4NnY-zV.d.ts +713 -0
- package/dist/browser/client-D4NnY-zV.d.ts.map +1 -0
- package/dist/browser/client-DQE0c7GI.d.ts +711 -0
- package/dist/browser/client-DQE0c7GI.d.ts.map +1 -0
- package/dist/browser/index.d.ts +3130 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/src-BrYPVbAN.js +2404 -0
- package/dist/browser/src-BrYPVbAN.js.map +1 -0
- package/dist/browser/src-t-4wp9-f.js +2400 -0
- package/dist/browser/src-t-4wp9-f.js.map +1 -0
- package/dist/node/api-types.d.ts +731 -0
- package/dist/node/api-types.d.ts.map +1 -0
- package/dist/node/api-types.js +281 -0
- package/dist/node/api-types.js.map +1 -0
- package/dist/node/crypto.d.ts +6 -0
- package/dist/node/crypto.d.ts.map +1 -0
- package/dist/node/crypto.js +103 -0
- package/dist/node/crypto.js.map +1 -0
- package/dist/node/definition.d.ts +638 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +315 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/oauth.js +124 -0
- package/dist/node/oauth.js.map +1 -0
- package/dist/node/routes.d.ts +1585 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +1100 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +99 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +16 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/server.d.ts +1585 -0
- package/dist/node/server.d.ts.map +1 -0
- package/dist/node/server.js +12 -0
- package/dist/node/server.js.map +1 -0
- package/dist/node/services.d.ts +10 -0
- package/dist/node/services.d.ts.map +1 -0
- package/dist/node/services.js +304 -0
- package/dist/node/services.js.map +1 -0
- package/dist/node/webhooks/auth.d.ts +133 -0
- package/dist/node/webhooks/auth.d.ts.map +1 -0
- package/dist/node/webhooks/auth.js +268 -0
- package/dist/node/webhooks/auth.js.map +1 -0
- package/dist/node/webhooks/verification.d.ts +102 -0
- package/dist/node/webhooks/verification.d.ts.map +1 -0
- package/dist/node/webhooks/verification.js +119 -0
- package/dist/node/webhooks/verification.js.map +1 -0
- package/package.json +136 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Copyright 2025 - present "ReJot Nederland B.V.", and individual contributors.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
4
|
+
associated documentation files (the “Software”), to deal in the Software without restriction,
|
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
7
|
+
furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
10
|
+
portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,250 @@
|
|
|
1
|
-
#
|
|
1
|
+
# api-fragment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@fragno-dev/api-fragment` is a Fragno fragment for configuring outbound HTTP API connections and
|
|
4
|
+
executing authenticated requests through server-side routes.
|
|
5
|
+
|
|
6
|
+
## Auth modes
|
|
7
|
+
|
|
8
|
+
- `none`
|
|
9
|
+
- static bearer token
|
|
10
|
+
- OAuth 2.0 authorization-code flow with PKCE
|
|
11
|
+
- OAuth 2.0 client-credentials flow
|
|
12
|
+
|
|
13
|
+
## Server setup
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createApiFragment } from "@fragno-dev/api-fragment/server";
|
|
17
|
+
|
|
18
|
+
const oauthRedirectUri = "https://app.example.com/api/integrations/oauth/callback";
|
|
19
|
+
const api = createApiFragment(
|
|
20
|
+
{
|
|
21
|
+
allowedBaseUrls: (url) => url.hostname.endsWith(".example.com"),
|
|
22
|
+
allowedOAuthRedirectUris: (url) => url.toString() === oauthRedirectUri,
|
|
23
|
+
onConnectionReadinessChanged: async ({ connectionId, ready }) => {
|
|
24
|
+
console.log("API connection readiness changed", connectionId, ready);
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
fragnoConfig,
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Always provide `allowedBaseUrls` in production so users cannot configure arbitrary server-side
|
|
32
|
+
request targets. OAuth start is denied unless `allowedOAuthRedirectUris` explicitly accepts the
|
|
33
|
+
callback URL.
|
|
34
|
+
|
|
35
|
+
## Client usage
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { createApiFragmentClient } from "@fragno-dev/api-fragment/react";
|
|
39
|
+
|
|
40
|
+
const api = createApiFragmentClient();
|
|
41
|
+
|
|
42
|
+
await api.createConnection.mutate(
|
|
43
|
+
{
|
|
44
|
+
baseUrl: "https://api.example.com",
|
|
45
|
+
auth: { type: "none" },
|
|
46
|
+
},
|
|
47
|
+
{ pathParams: { slug: "example" } },
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Routes
|
|
52
|
+
|
|
53
|
+
Connection management:
|
|
54
|
+
|
|
55
|
+
- `PUT /connections/:slug` - create a connection with an explicit slug.
|
|
56
|
+
- `GET /connections` - list configured connections.
|
|
57
|
+
- `GET /connections/:slug` - read one connection.
|
|
58
|
+
- `DELETE /connections/:slug` - delete connection, auth secrets, and pending OAuth state.
|
|
59
|
+
|
|
60
|
+
Auth:
|
|
61
|
+
|
|
62
|
+
- `GET /connections/:slug/auth/status`
|
|
63
|
+
- `POST /connections/:slug/auth/token` - store or replace a bearer token.
|
|
64
|
+
- `POST /connections/:slug/auth/oauth/start` - start authorization-code + PKCE with an explicit
|
|
65
|
+
`redirectUri` query parameter.
|
|
66
|
+
- `GET /oauth/callback` - complete authorization-code + PKCE.
|
|
67
|
+
- `DELETE /connections/:slug/auth`
|
|
68
|
+
|
|
69
|
+
Requests:
|
|
70
|
+
|
|
71
|
+
- `POST /connections/:slug/request` - execute an HTTP request against the configured `baseUrl`.
|
|
72
|
+
|
|
73
|
+
Webhook endpoints:
|
|
74
|
+
|
|
75
|
+
- `PUT /webhooks/endpoints/:endpointId` - create or replace an endpoint.
|
|
76
|
+
- `GET /webhooks/endpoints/:endpointId/events` - answer configured GET verification challenges.
|
|
77
|
+
- `POST /webhooks/endpoints/:endpointId/events` - answer configured POST verification challenges or
|
|
78
|
+
accept a delivery.
|
|
79
|
+
|
|
80
|
+
Each endpoint's `deliveries` reports the last accepted request (a delivery or an answered challenge)
|
|
81
|
+
and the last rejected one with its error code. Any configuration write clears both, so they describe
|
|
82
|
+
only the current configuration. Rejections need no credentials, so anyone with the URL can record
|
|
83
|
+
one; treat them as diagnostics.
|
|
84
|
+
|
|
85
|
+
The request route only accepts relative paths. Caller-provided `Authorization` headers are ignored
|
|
86
|
+
and replaced with stored connection auth.
|
|
87
|
+
|
|
88
|
+
## Bearer auth
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
await api.createConnection.mutate(
|
|
92
|
+
{
|
|
93
|
+
baseUrl: "https://api.example.com",
|
|
94
|
+
auth: { type: "none" },
|
|
95
|
+
},
|
|
96
|
+
{ pathParams: { slug: "example" } },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await api.setBearerToken.mutate({ token: "..." }, { pathParams: { slug: "example" } });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Client credentials auth
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
await api.createConnection.mutate(
|
|
106
|
+
{
|
|
107
|
+
baseUrl: "https://api.example.com",
|
|
108
|
+
auth: {
|
|
109
|
+
type: "client_credentials",
|
|
110
|
+
tokenEndpoint: "https://auth.example.com/oauth/token",
|
|
111
|
+
clientId: "...",
|
|
112
|
+
clientSecret: "...",
|
|
113
|
+
tokenEndpointAuthMethod: "client_secret_basic",
|
|
114
|
+
scopes: ["read"],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{ pathParams: { slug: "machine" } },
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The fragment acquires a token on the first API request, stores it with expiry, reuses it while
|
|
122
|
+
valid, and refreshes it when it is close to expiring.
|
|
123
|
+
|
|
124
|
+
## OAuth authorization code auth
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await api.createConnection.mutate(
|
|
128
|
+
{
|
|
129
|
+
baseUrl: "https://api.example.com",
|
|
130
|
+
auth: {
|
|
131
|
+
type: "oauth",
|
|
132
|
+
authorizationEndpoint: "https://auth.example.com/oauth/authorize",
|
|
133
|
+
tokenEndpoint: "https://auth.example.com/oauth/token",
|
|
134
|
+
clientId: "...",
|
|
135
|
+
clientSecret: "...",
|
|
136
|
+
tokenEndpointAuthMethod: "client_secret_basic",
|
|
137
|
+
scopes: ["read"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{ pathParams: { slug: "oauth-api" } },
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const start = await api.startOAuth.mutate(
|
|
144
|
+
{},
|
|
145
|
+
{
|
|
146
|
+
pathParams: { slug: "oauth-api" },
|
|
147
|
+
query: { redirectUri: oauthRedirectUri },
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
window.location.href = start.authorizationUrl;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The callback route stores access/refresh tokens; when the connection was not ready before, it
|
|
154
|
+
triggers `onConnectionReadinessChanged` with `ready: true`.
|
|
155
|
+
|
|
156
|
+
## Executing requests
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const response = await api.request.mutate(
|
|
160
|
+
{
|
|
161
|
+
method: "GET",
|
|
162
|
+
path: "/v1/profile",
|
|
163
|
+
query: { include: "teams" },
|
|
164
|
+
},
|
|
165
|
+
{ pathParams: { slug: "oauth-api" } },
|
|
166
|
+
);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Response bodies are parsed as JSON when the upstream response has a JSON content type. Other
|
|
170
|
+
responses are returned as text. Binary responses are out of scope for the MVP.
|
|
171
|
+
|
|
172
|
+
## Webhook verification challenges
|
|
173
|
+
|
|
174
|
+
Webhook verification runs after endpoint authentication and before delivery ID extraction. A matched
|
|
175
|
+
challenge returns synchronously and does not trigger `onWebhookReceived`.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
await api.createWebhookEndpoint.mutate(
|
|
179
|
+
{
|
|
180
|
+
name: "Slack",
|
|
181
|
+
status: "active",
|
|
182
|
+
verification: {
|
|
183
|
+
type: "challenge",
|
|
184
|
+
method: "POST",
|
|
185
|
+
when: {
|
|
186
|
+
type: "equals",
|
|
187
|
+
source: { type: "jsonBodyPath", path: ["type"] },
|
|
188
|
+
value: "url_verification",
|
|
189
|
+
},
|
|
190
|
+
response: {
|
|
191
|
+
type: "echoText",
|
|
192
|
+
source: { type: "jsonBodyPath", path: ["challenge"] },
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
deliveryIdentity: { type: "jsonBodyPath", path: ["event_id"] },
|
|
196
|
+
auth: {
|
|
197
|
+
type: "hmac",
|
|
198
|
+
secret: "signing-secret",
|
|
199
|
+
algorithm: "sha256",
|
|
200
|
+
signature: {
|
|
201
|
+
location: "header",
|
|
202
|
+
name: "x-slack-signature",
|
|
203
|
+
encoding: "hex",
|
|
204
|
+
prefix: "v0=",
|
|
205
|
+
},
|
|
206
|
+
signedPayload: {
|
|
207
|
+
type: "timestampedBody",
|
|
208
|
+
prefix: "v0:",
|
|
209
|
+
timestampHeader: "x-slack-request-timestamp",
|
|
210
|
+
delimiter: ":",
|
|
211
|
+
toleranceSeconds: 300,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{ pathParams: { endpointId: "slack" } },
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Request value sources can read a header, query parameter, or JSON body path. Challenge predicates
|
|
220
|
+
can require that a value is present or equals a configured string. `echoText` responds with the
|
|
221
|
+
selected value as `text/plain`.
|
|
222
|
+
|
|
223
|
+
## Hooks
|
|
224
|
+
|
|
225
|
+
- `onConnectionChanged` receives `{ connectionId, connection }`.
|
|
226
|
+
- `onConnectionDeleted` receives `{ connectionId, previous }`.
|
|
227
|
+
- `onConnectionReadinessChanged` receives `{ connectionId, ready }` when a write changes whether
|
|
228
|
+
stored auth can authorize requests: on creation, and afterwards whenever credentials, consent, or
|
|
229
|
+
token changes flip readiness. It also fires when an access token without a refresh token expires.
|
|
230
|
+
- `onWebhookEndpointChanged` receives `{ change: "created", endpointId, endpoint }`,
|
|
231
|
+
`{ change: "updated", endpointId, endpoint, previousStatus }`, or
|
|
232
|
+
`{ change: "deleted", endpointId }`. Compare `previousStatus` with `endpoint.status` to tell
|
|
233
|
+
whether a write changed if the endpoint accepts deliveries.
|
|
234
|
+
- `onWebhookReceived` receives the authenticated webhook payload and its derived delivery ID.
|
|
235
|
+
|
|
236
|
+
Durable hook callbacks, including `onWebhookReceived`, may execute concurrently and may complete out
|
|
237
|
+
of delivery order. Use the delivery ID for idempotency and use an upstream sequence or version when
|
|
238
|
+
the provider's event order affects state.
|
|
239
|
+
|
|
240
|
+
Hook callbacks receive the durable hook context as their second argument, including
|
|
241
|
+
`idempotencyKey`, `hookId`, retry metadata, and transaction access.
|
|
242
|
+
|
|
243
|
+
## Security notes
|
|
244
|
+
|
|
245
|
+
- Use `allowedBaseUrls` in production to avoid SSRF.
|
|
246
|
+
- Request paths must be relative; absolute request URLs are rejected.
|
|
247
|
+
- Stored auth replaces caller-provided `Authorization` headers.
|
|
248
|
+
- Secrets and tokens are stored in the fragment database as JSON/plaintext for MVP.
|
|
249
|
+
- OAuth state is one-time use and expires after 10 minutes.
|
|
250
|
+
- Do not log secret payloads, authorization headers, refresh tokens, or token endpoint responses.
|