@andco/sdk 0.0.2 → 0.0.4
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 +1 -1
- package/README.md +96 -54
- package/dist/auth.d.ts +8 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +32 -23
- package/dist/browser/controller.d.ts +55 -1
- package/dist/browser/controller.d.ts.map +1 -1
- package/dist/browser/controller.js +166 -9
- package/dist/browser/frame.d.ts +4 -3
- package/dist/browser/frame.d.ts.map +1 -1
- package/dist/browser/frame.js +12 -20
- package/dist/browser/index.d.ts +18 -6
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +64 -29
- package/dist/browser/popup.d.ts +6 -1
- package/dist/browser/popup.d.ts.map +1 -1
- package/dist/browser/popup.js +37 -8
- package/dist/cli/index.d.ts +4 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -3
- package/dist/cli/server.d.ts +5 -0
- package/dist/cli/server.d.ts.map +1 -1
- package/dist/cli/server.js +5 -0
- package/dist/client.d.ts +66 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +125 -74
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/credentials.d.ts +58 -4
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +0 -0
- package/dist/errors.d.ts +23 -21
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -20
- package/dist/globals.d.ts +25 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +15 -0
- package/dist/grants-api.d.ts +34 -0
- package/dist/grants-api.d.ts.map +1 -0
- package/dist/grants-api.js +48 -0
- package/dist/grants.d.ts +16 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +13 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/inflight.d.ts +31 -0
- package/dist/inflight.d.ts.map +1 -0
- package/dist/inflight.js +26 -0
- package/dist/intents.d.ts +386 -40
- package/dist/intents.d.ts.map +1 -1
- package/dist/intents.js +712 -53
- package/dist/oauth.d.ts +55 -6
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +86 -57
- package/dist/presenter.d.ts +59 -11
- package/dist/presenter.d.ts.map +1 -1
- package/dist/presenter.js +40 -1
- package/dist/resource.d.ts +109 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +151 -0
- package/dist/rest.d.ts +23 -4
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +46 -5
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server-metadata.generated.d.ts.map +1 -1
- package/dist/server-metadata.generated.js +8 -4
- package/dist/service.d.ts +109 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +241 -0
- package/dist/session-store.d.ts +12 -4
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +64 -14
- package/dist/storage.d.ts +16 -23
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +27 -25
- package/dist/tokens.d.ts +46 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +148 -0
- package/package.json +13 -3
package/LICENSE
CHANGED
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
same "printed page" as the copyright notice for easier
|
|
188
188
|
identification within third-party archives.
|
|
189
189
|
|
|
190
|
-
Copyright 2026
|
|
190
|
+
Copyright 2026 Haulmer
|
|
191
191
|
|
|
192
192
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
193
|
you may not use this file except in compliance with the License.
|
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# `@andco/sdk`
|
|
2
2
|
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> **Pre-1.0 software.** Until version 1.0.0 is released, any new version may include breaking
|
|
5
|
+
> changes, including minor and patch releases. Expect any API incompatibility and data resets
|
|
6
|
+
> in the Andco environments without prior notice. Pin an exact version and review the release
|
|
7
|
+
> notes before upgrading.
|
|
8
|
+
|
|
3
9
|
The core AndCo SDK: one client shape (`AndcoClient`) with a builder per runtime — browser,
|
|
4
|
-
server, and CLI. There is no separate `@andco/sdk-js` package; this package is the universal
|
|
10
|
+
server, and CLI. There is no separate `@andco/andco-sdk-js` package; this package is the universal
|
|
5
11
|
one, and its `./browser`, `./server`, and `./cli` subpaths are how it adapts to each runtime.
|
|
6
12
|
|
|
7
13
|
## Install
|
|
@@ -61,9 +67,8 @@ const andco = createAndcoInstanceForCLI({
|
|
|
61
67
|
const { data: session } = await andco.auth.signIn();
|
|
62
68
|
```
|
|
63
69
|
|
|
64
|
-
Every builder returns an `AndcoClient`. There
|
|
65
|
-
|
|
66
|
-
client shapes now.
|
|
70
|
+
Every builder returns an `AndcoClient`. There are two client shapes: `AndcoClient`, the
|
|
71
|
+
unauthenticated arm, and `AndcoClientAuthed`, the authenticated one.
|
|
67
72
|
|
|
68
73
|
## Public, no credentials involved
|
|
69
74
|
|
|
@@ -75,7 +80,7 @@ const { data, error } = await andco.rest.http.GET("/catalog/permissions");
|
|
|
75
80
|
if (error) throw error;
|
|
76
81
|
```
|
|
77
82
|
|
|
78
|
-
##
|
|
83
|
+
## Attaching credentials
|
|
79
84
|
|
|
80
85
|
Two ways to get an `AndcoClientAuthed`:
|
|
81
86
|
|
|
@@ -84,8 +89,8 @@ Two ways to get an `AndcoClientAuthed`:
|
|
|
84
89
|
for the common server case where a handler already pulled a token out of its own session:
|
|
85
90
|
|
|
86
91
|
```ts
|
|
87
|
-
const
|
|
88
|
-
const { data } = await
|
|
92
|
+
const client = andco.with(await accessTokenFromCookie(request.headers));
|
|
93
|
+
const { data } = await client.rest.http.GET("/userinfo");
|
|
89
94
|
```
|
|
90
95
|
|
|
91
96
|
- `andco.authorized()` returns the instance's own session, seen as authorized — a fresh reference
|
|
@@ -98,21 +103,23 @@ Two ways to get an `AndcoClientAuthed`:
|
|
|
98
103
|
|
|
99
104
|
`AndcoCredentials` is the entire contract the SDK needs for authorization:
|
|
100
105
|
|
|
101
|
-
```ts
|
|
106
|
+
```ts nocheck
|
|
102
107
|
export interface AndcoCredentials {
|
|
103
108
|
accessTokenFor(resource: string): Promise<string | null>;
|
|
109
|
+
readonly config?: AndcoConfig;
|
|
104
110
|
}
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
`config` is optional: `andco.with()`, `andco.authorized()` and `service.credentials()` carry it, so
|
|
114
|
+
`new Bank(credentials)` takes its client id and endpoint from the credentials instead of declaring
|
|
115
|
+
them again. Any object with `accessTokenFor` works — a Better Auth account, a Passport session, a row in your
|
|
108
116
|
own table, or a plain closure — without adopting the SDK's own persistence.
|
|
109
117
|
|
|
110
118
|
## Resource Server Definitions
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
implements:
|
|
120
|
+
A Resource Server Definition is any object that implements:
|
|
114
121
|
|
|
115
|
-
```ts
|
|
122
|
+
```ts nocheck
|
|
116
123
|
interface AndcoResourceServer<Client> {
|
|
117
124
|
readonly id: string;
|
|
118
125
|
readonly resource: string;
|
|
@@ -127,67 +134,102 @@ satisfy `AndcoCredentials` and can be passed straight to `use(...)`.
|
|
|
127
134
|
|
|
128
135
|
## Intents
|
|
129
136
|
|
|
130
|
-
`andco.intents` is domain-neutral
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
`andco.intents` is the domain-neutral lifecycle of an Intent: read, present, approve, reject,
|
|
138
|
+
cancel and follow it. Creating one belongs to the Resource Server Definition that owns the domain —
|
|
139
|
+
`bank.transfers.create`, `bank.deposits.create`, `bank.accounts.create` in
|
|
140
|
+
[`@andco/bank-sdk`](https://www.npmjs.com/package/@andco/bank-sdk) — composed over this same
|
|
141
|
+
surface (see [Resource Server Definitions](#resource-server-definitions) above). Every method takes
|
|
142
|
+
the Intent or its id.
|
|
135
143
|
|
|
136
144
|
```ts
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
145
|
+
import { Bank } from "@andco/bank-sdk";
|
|
146
|
+
|
|
147
|
+
const bank = new Bank(andco.authorized());
|
|
148
|
+
|
|
149
|
+
const { data: deposit, error } = await bank.deposits.create(
|
|
150
|
+
{ amount: { currency: "CLP", value: "25990" } },
|
|
151
|
+
{ idempotencyKey: "deposit:8472" },
|
|
143
152
|
);
|
|
144
153
|
if (error) throw error;
|
|
154
|
+
|
|
155
|
+
const presentation = andco.intents.present(deposit, { presentation: "popup" });
|
|
156
|
+
const { data } = await presentation.result;
|
|
157
|
+
if (data?.outcome === "complete") console.log("[deposit] %s", data.intent.status);
|
|
145
158
|
```
|
|
146
159
|
|
|
147
|
-
- `
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- `
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
`
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
- `present(intent | id | factory, { presentation, returnTo, errorReturnTo, signal })` — returns a
|
|
161
|
+
controller `{ status, result, close() }` at once. `presentation` is `"popup"` (default),
|
|
162
|
+
`"newtab"` or `"redirect"`. The window opens inside the click that called it; `result` settles
|
|
163
|
+
with `{ outcome, intent }`, the Intent read again afterwards. With `"redirect"` the document is
|
|
164
|
+
replaced and `result` never settles. The SDK always generates and checks `state`.
|
|
165
|
+
- `fromCallback(url, { state? })` — on the return page of a `"redirect"` or `"newtab"`, reads the
|
|
166
|
+
outcome and the Intent. It refuses a `state` this browser did not start (`invalid_callback`) and
|
|
167
|
+
consumes each presentation once.
|
|
168
|
+
- `presentationURL(intent, { presentation })` — synchronous URL that presents the Intent, with its
|
|
169
|
+
callbacks bound; `presentationURLFor(intentId, options)` reads the Intent first. Never intercepted
|
|
170
|
+
by a native Host.
|
|
171
|
+
- `get(intent)` — reads the authoritative state of one Intent, as `Result<AndcoIntent>`.
|
|
172
|
+
- `approve(intent, { grant? })` — approves with this credential's Grant, which must hold `approve`
|
|
173
|
+
on the Intent's family, within its maximum. Fails with `amount_out_of_grant` above the maximum
|
|
174
|
+
(the Intent stays pending for another approver) and `intent_expired` past its deadline.
|
|
175
|
+
- `reject(intent, { reason })` — rejects as an approver; `cancel(intent, { reason })` withdraws it as
|
|
176
|
+
the creator. The reason is required, 1–280 characters.
|
|
177
|
+
- `subscribe(intent, [{ types, after, onCheckpoint, onError, pollIntervalMs }], handler)` — follows
|
|
178
|
+
the Intent's durable facts in order. Returns an unsubscribe function.
|
|
179
|
+
- `events(intent, after?, signal?)` — reads one page of durable Intent facts.
|
|
180
|
+
- `create(type, input, options?)` and `createSigned(type, instruction)` — the generic creation a
|
|
181
|
+
third-party Resource Server Definition composes. `type` is sent as the body's own `type` and
|
|
182
|
+
checked against the response.
|
|
183
|
+
|
|
184
|
+
A Service (`createAndcoService` from `@andco/sdk/server`) gets the same surface, bound to its
|
|
185
|
+
Client Credentials for one Organization, with `service.intents({ subject?, grant? })`:
|
|
162
186
|
|
|
163
187
|
```ts
|
|
164
|
-
|
|
188
|
+
await service.intents({ subject: { type: "organization", identifier: "123" } }).approve(transferId);
|
|
189
|
+
```
|
|
165
190
|
|
|
166
|
-
|
|
167
|
-
|
|
191
|
+
The browser must retrieve the authoritative Intent state after presentation; callback parameters
|
|
192
|
+
are correlation data, not financial state. In React, `useIntent(intentId | null)` from
|
|
193
|
+
`@andco/sdk-react` tracks an Intent until it reaches a terminal status.
|
|
168
194
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
## Tokens, Grants and Resource Servers
|
|
196
|
+
|
|
197
|
+
A Resource Server of your own verifies what reaches it with the same instance:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const { data: claims, error } = await andco.tokens.verify(bearer, {
|
|
201
|
+
audience: "https://api.casa-norte.example",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const { data: grant } = await andco.grants.get(bearer);
|
|
205
|
+
|
|
206
|
+
const orders = andco.resource({ resource: "https://api.casa-norte.example" });
|
|
207
|
+
export async function GET(request: Request) {
|
|
208
|
+
const outcome = await orders.protect({ scopes: ["orders:read"] })(request);
|
|
209
|
+
if (outcome instanceof Response) return outcome; // 401 or 403 with WWW-Authenticate
|
|
210
|
+
return Response.json({ subject: outcome.data?.subject });
|
|
211
|
+
}
|
|
176
212
|
```
|
|
177
213
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
- `tokens.verify(token, { audience?, clockToleranceSeconds? })` verifies signature (RS256/ES256),
|
|
215
|
+
`exp`, `nbf`, `iss` and `aud` offline against the Authorization Server's JWKS, cached by `kid`.
|
|
216
|
+
- `grants.get(token)` reads the live Grant behind a token (`GET /authorization`), in its Grant form.
|
|
217
|
+
- `resource({ resource }).protect({ scopes, authorizationDetails })` is a guard that answers
|
|
218
|
+
`401`/`403` with `WWW-Authenticate`; `.hono()` and `.express()` adapt it to a framework.
|
|
219
|
+
|
|
220
|
+
Concurrent token refreshes share one in-flight promise through `AndcoInflight` (`MemoryInflight` by
|
|
221
|
+
default; pass your own as `inflight` to share it across processes). `auth.refresh()` goes through
|
|
222
|
+
it too.
|
|
181
223
|
|
|
182
224
|
## Exports
|
|
183
225
|
|
|
184
226
|
- `@andco/sdk` — `AndcoClient`, `AndcoAuth`, `AndcoConfig`, credential types and helpers,
|
|
185
|
-
`AndcoIntents`, `AndcoOAuth`, `AndcoRest`, `AndcoSessionStore`, storage adapters, and error
|
|
227
|
+
`AndcoIntents`, `AndcoTokens`, `AndcoGrants`, `AndcoOAuth`, `AndcoRest`, `AndcoSessionStore`, storage adapters, and error
|
|
186
228
|
types. See `packages/sdk/src/index.ts` for the exhaustive list.
|
|
187
229
|
- `@andco/sdk/browser` — `createAndcoInstanceForBrowser`, `browserPresenter`, the popup/iframe
|
|
188
230
|
bridge, and the Andco Button controller.
|
|
189
231
|
- `@andco/sdk/server` — `createAndcoInstanceForServer`.
|
|
190
|
-
- `@andco/sdk/cli` — `createAndcoInstanceForCLI`, `
|
|
232
|
+
- `@andco/sdk/cli` — `createAndcoInstanceForCLI`, `AndcoPresenterCli`.
|
|
191
233
|
|
|
192
234
|
There is intentionally no wildcard subpath export. This prevents internal files and runtime-only
|
|
193
235
|
implementations from becoming accidental public API.
|
|
@@ -196,6 +238,6 @@ implementations from becoming accidental public API.
|
|
|
196
238
|
|
|
197
239
|
- Never place confidential OAuth client secrets in browser code.
|
|
198
240
|
- Use a distinct OAuth client for each environment.
|
|
199
|
-
- Request the smallest
|
|
241
|
+
- Request the smallest permissions needed by the current operation: OIDC scopes only for identity, and a Grant Request per resource.
|
|
200
242
|
- Preserve idempotency keys across retries of the same financial operation.
|
|
201
243
|
- Treat all returned identity, account, and profile fields as personal data.
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AndcoConfig } from "./config.js";
|
|
2
2
|
import type { AndcoSession } from "./credentials.js";
|
|
3
3
|
import { Result } from "./errors.js";
|
|
4
|
+
import { type AndcoGlobals } from "./globals.js";
|
|
4
5
|
import type { AndcoAuthorizationOptions, AndcoAuthorizationRequest, AndcoOAuth } from "./oauth.js";
|
|
5
6
|
import type { AndcoPresentation, AndcoPresenter } from "./presenter.js";
|
|
6
7
|
import type { AndcoSessionStore } from "./session-store.js";
|
|
8
|
+
/** Authorization parameters plus the presentation used to start sign-in. */
|
|
7
9
|
export type AndcoSignInOptions = AndcoAuthorizationOptions & {
|
|
8
10
|
presentation?: AndcoPresentation;
|
|
9
11
|
};
|
|
12
|
+
/** Collaborators that prepare, present, exchange, and persist one authorization. */
|
|
10
13
|
export type AndcoAuthOptions = {
|
|
14
|
+
/** Read for the registered callback, which has to be known before the request is prepared. */
|
|
15
|
+
config: AndcoConfig;
|
|
11
16
|
oauth: AndcoOAuth;
|
|
12
17
|
store: AndcoSessionStore;
|
|
13
18
|
presenter: AndcoPresenter;
|
|
14
19
|
/** Generates the Presentation ID. Injected so a test can make one deterministic. */
|
|
15
20
|
randomId?: () => string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
21
|
+
/** Platform capabilities; `crypto` sources the Presentation ID. Defaults to the runtime's own. */
|
|
22
|
+
globals?: AndcoGlobals;
|
|
18
23
|
/**
|
|
19
24
|
* Persists a transaction before it is presented.
|
|
20
25
|
*
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAqB,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,cAAc,CAAC;AACjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,GAAG;IAC3D,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,8FAA8F;IAC9F,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;IACxB,kGAAkG;IAClG,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnF,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAS;;gBAID,OAAO,EAAE,gBAAgB;IAK5C;;;;;OAKG;IACU,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IA4C3F,iGAAiG;IACpF,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAI7C;;;;;;;;OAQG;IACI,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;IAK7E,gFAAgF;IACnE,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAO/D,uFAAuF;IAC1E,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;CAGtD"}
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { RANDOM_UUID } from "@andco/protocol";
|
|
1
|
+
import { RANDOM_UUID, SafeURL } from "@andco/protocol";
|
|
2
2
|
import { ANDCO_ERROR_CODES, Result } from "./errors.js";
|
|
3
|
+
import { resolveGlobals } from "./globals.js";
|
|
3
4
|
/**
|
|
4
5
|
* Sign-in for a runtime that owns exactly one session.
|
|
5
6
|
*
|
|
@@ -19,8 +20,10 @@ import { ANDCO_ERROR_CODES, Result } from "./errors.js";
|
|
|
19
20
|
*/
|
|
20
21
|
export class AndcoAuth {
|
|
21
22
|
#options;
|
|
23
|
+
#globals;
|
|
22
24
|
constructor(options) {
|
|
23
25
|
this.#options = options;
|
|
26
|
+
this.#globals = options.globals ?? resolveGlobals();
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Runs one authorization end to end.
|
|
@@ -29,17 +32,32 @@ export class AndcoAuth {
|
|
|
29
32
|
* result arrives on the next page load, through the store's own callback consumption.
|
|
30
33
|
*/
|
|
31
34
|
async signIn(options = {}) {
|
|
32
|
-
const { oauth, presenter, store } = this.#options;
|
|
33
|
-
const presentationId = this.#options.randomId ? this.#options.randomId() : RANDOM_UUID(this.#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
const { config, oauth, presenter, store } = this.#options;
|
|
36
|
+
const presentationId = this.#options.randomId ? this.#options.randomId() : RANDOM_UUID(this.#globals.crypto);
|
|
37
|
+
// The callback has to be known before the request is prepared, because the presenter needs it
|
|
38
|
+
// to open its window. It is registered configuration, so it is knowable without the round trip.
|
|
39
|
+
const configured = options.redirectTo ?? config.redirectTo;
|
|
40
|
+
if (!configured) {
|
|
41
|
+
return Result.fail(ANDCO_ERROR_CODES.INVALID_CONFIGURATION, {
|
|
42
|
+
message: "`redirectTo` is required, on the instance or on the request",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Deferred: preparing the request may push it (PAR), and a presenter has to open its window
|
|
46
|
+
// during the user activation rather than after a network round trip.
|
|
47
|
+
let prepared;
|
|
39
48
|
const presented = await presenter.present({
|
|
40
|
-
|
|
49
|
+
kind: "oauth",
|
|
50
|
+
url: async () => {
|
|
51
|
+
const request = await oauth.createAuthorizationRequest(options);
|
|
52
|
+
if (request.error) {
|
|
53
|
+
throw request.error;
|
|
54
|
+
}
|
|
55
|
+
await this.#options.persistTransaction?.(request.data);
|
|
56
|
+
prepared = request.data;
|
|
57
|
+
return request.data.authorizationUrl;
|
|
58
|
+
},
|
|
41
59
|
presentation: options.presentation ?? "popup",
|
|
42
|
-
returnTo,
|
|
60
|
+
returnTo: new SafeURL(configured),
|
|
43
61
|
presentationId,
|
|
44
62
|
});
|
|
45
63
|
if (presented.error)
|
|
@@ -47,7 +65,9 @@ export class AndcoAuth {
|
|
|
47
65
|
// Either the document was replaced, or the user dismissed. Neither is an error.
|
|
48
66
|
if (!presented.data)
|
|
49
67
|
return Result.ok(null);
|
|
50
|
-
|
|
68
|
+
if (!prepared)
|
|
69
|
+
return Result.fail(ANDCO_ERROR_CODES.INVALID_CALLBACK, { message: "no transaction was prepared" });
|
|
70
|
+
const exchanged = await oauth.exchangeCallback({ callbackUrl: presented.data, request: prepared });
|
|
51
71
|
if (exchanged.error)
|
|
52
72
|
return Result.fail(exchanged.error);
|
|
53
73
|
const written = await store.set(exchanged.data);
|
|
@@ -82,17 +102,6 @@ export class AndcoAuth {
|
|
|
82
102
|
}
|
|
83
103
|
/** Refreshes now rather than waiting for a token request to find the session stale. */
|
|
84
104
|
async refresh() {
|
|
85
|
-
|
|
86
|
-
await store.ready;
|
|
87
|
-
const current = store.getSnapshot();
|
|
88
|
-
if (!current?.refreshToken)
|
|
89
|
-
return Result.fail(ANDCO_ERROR_CODES.SESSION_MISSING);
|
|
90
|
-
const refreshed = await this.#options.oauth.refresh(current.refreshToken);
|
|
91
|
-
if (refreshed.error)
|
|
92
|
-
return Result.fail(refreshed.error);
|
|
93
|
-
const written = await store.set(refreshed.data);
|
|
94
|
-
if (written.error)
|
|
95
|
-
return Result.fail(written.error);
|
|
96
|
-
return Result.ok(refreshed.data);
|
|
105
|
+
return this.#options.store.refresh();
|
|
97
106
|
}
|
|
98
107
|
}
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import type { AndCoClickEvent, AndCoErrorEvent, AndCoReadyEvent, AndCoThemeMode } from "@andco/protocol";
|
|
2
2
|
import type { AndcoClient } from "../client.js";
|
|
3
3
|
import { AndcoError } from "../errors.js";
|
|
4
|
+
import type { AndcoIntentId, AndcoPresentationResult } from "../intents.js";
|
|
4
5
|
import type { AndcoAuthorizationOptions } from "../oauth.js";
|
|
5
6
|
import type { AndcoPresentation } from "../presenter.js";
|
|
6
7
|
/** The hosted workflow an iframe presents. */
|
|
7
8
|
export type AndcoButtonFlow = "authorization" | "intent";
|
|
8
9
|
/** Observable lifecycle of one hosted Andco surface. */
|
|
9
10
|
export type AndcoButtonStatus = "idle" | "connecting" | "ready" | "authorizing" | "complete" | "dismissed" | "error";
|
|
11
|
+
/** Hosted surface URL, lifecycle status, and the most recent failure. */
|
|
10
12
|
export type AndcoButtonSnapshot = {
|
|
11
13
|
status: AndcoButtonStatus;
|
|
12
14
|
iframeURL: string;
|
|
13
15
|
ready: boolean;
|
|
14
16
|
error: AndcoError | null;
|
|
15
17
|
};
|
|
18
|
+
/** Hosted control configuration and callbacks for authorization or Intent presentation. */
|
|
16
19
|
export type AndcoButtonControllerOptions = {
|
|
17
20
|
/** The instance whose configuration, protocol, and session this surface acts on. */
|
|
18
21
|
client: AndcoClient;
|
|
22
|
+
/** Authorization by default; `intent` requires an `intentId`. */
|
|
19
23
|
flow?: AndcoButtonFlow;
|
|
24
|
+
/**
|
|
25
|
+
* Required when `flow` is `"intent"`. Resolved after the user activates, never before: the iframe
|
|
26
|
+
* holds the activation and opens the window first, so creating the Intent earlier would make one
|
|
27
|
+
* on every render and lose the activation that the window needs.
|
|
28
|
+
*/
|
|
29
|
+
intentId?: AndcoIntentId;
|
|
20
30
|
/** Widget release to load. Defaults to the release the SDK was built against. */
|
|
21
31
|
release?: number;
|
|
22
32
|
/** Changes while the application runs, unlike endpoints and versions. */
|
|
@@ -28,13 +38,20 @@ export type AndcoButtonControllerOptions = {
|
|
|
28
38
|
presentation?: AndcoPresentation;
|
|
29
39
|
/** Request-local authorization options, applied when the user activates the surface. */
|
|
30
40
|
authorization?: AndcoAuthorizationOptions;
|
|
41
|
+
/** Runs after the hosted surface completes its handshake. */
|
|
31
42
|
onReady?: (event: AndCoReadyEvent) => void;
|
|
43
|
+
/** Runs when the user activates the hosted control, before authorization starts. */
|
|
32
44
|
onActivate?: (event: AndCoClickEvent) => void;
|
|
33
|
-
|
|
45
|
+
/** Carries the Presentation Result on an Intent flow, and nothing on an authorization. */
|
|
46
|
+
onComplete?: (outcome?: AndcoPresentationResult) => void;
|
|
47
|
+
/** Runs when the user closes or declines the presentation. */
|
|
34
48
|
onDismiss?: () => void;
|
|
49
|
+
/** Receives lifecycle failures; use the error code for diagnostics. */
|
|
35
50
|
onError?: (error: AndcoError, event?: AndCoErrorEvent) => void;
|
|
36
51
|
};
|
|
37
52
|
type Listener = (snapshot: AndcoButtonSnapshot) => void;
|
|
53
|
+
/** Renders a style record — property names only, no camelCase — as inline CSS text. */
|
|
54
|
+
declare function frameStyleText(style: Readonly<Record<string, string>>): string;
|
|
38
55
|
/**
|
|
39
56
|
* Coordinates one hosted Andco iframe without owning how a framework renders it.
|
|
40
57
|
*
|
|
@@ -45,6 +62,15 @@ type Listener = (snapshot: AndcoButtonSnapshot) => void;
|
|
|
45
62
|
* Render `snapshot.iframeURL` in your platform's own iframe element, then hand that element to
|
|
46
63
|
* `connect()` once it mounts.
|
|
47
64
|
*
|
|
65
|
+
* The frame's fixed attributes and default sizing are published as static data —
|
|
66
|
+
* {@link AndcoButtonController.IFRAME_SANDBOX}, {@link AndcoButtonController.IFRAME_REFERRER_POLICY},
|
|
67
|
+
* {@link AndcoButtonController.IFRAME_DEFAULT_STYLE}, and {@link AndcoButtonController.frameProps} for
|
|
68
|
+
* the snapshot-derived `src`/`hidden` pair — so every binding applies the same contract to its own
|
|
69
|
+
* element instead of redeclaring it. `sdk-script`'s custom element is the one exception on sizing: it
|
|
70
|
+
* sizes itself through a stylesheet layer (using {@link AndcoButtonController.DEFAULT_SIZE}, so the
|
|
71
|
+
* visible default stays the same) and stretches its internal iframe to fill it, so that an author's
|
|
72
|
+
* own CSS on `andco-button` wins without touching the iframe's inline style.
|
|
73
|
+
*
|
|
48
74
|
* @example
|
|
49
75
|
* ```ts
|
|
50
76
|
* const controller = new AndcoButtonController({ client: andco, onComplete: reload });
|
|
@@ -54,12 +80,39 @@ type Listener = (snapshot: AndcoButtonSnapshot) => void;
|
|
|
54
80
|
*/
|
|
55
81
|
export declare class AndcoButtonController {
|
|
56
82
|
#private;
|
|
83
|
+
/** @see {@link IFRAME_SANDBOX} */
|
|
84
|
+
static readonly IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox";
|
|
85
|
+
/** @see {@link IFRAME_REFERRER_POLICY} */
|
|
86
|
+
static readonly IFRAME_REFERRER_POLICY = "no-referrer";
|
|
87
|
+
/** @see {@link IFRAME_BASE_STYLE} */
|
|
88
|
+
static readonly IFRAME_BASE_STYLE: Readonly<Record<string, string>>;
|
|
89
|
+
/** @see {@link DEFAULT_SIZE} */
|
|
90
|
+
static readonly DEFAULT_SIZE: Readonly<Record<string, string>>;
|
|
91
|
+
/** @see {@link IFRAME_DEFAULT_STYLE} */
|
|
92
|
+
static readonly IFRAME_DEFAULT_STYLE: Readonly<Record<string, string>>;
|
|
93
|
+
/**
|
|
94
|
+
* Renders a style record as inline CSS text, for a binding that needs a string rather than an
|
|
95
|
+
* object — Svelte's `style` attribute and `sdk-script`'s `HTMLElement.style.cssText`.
|
|
96
|
+
*/
|
|
97
|
+
static readonly frameStyleText: typeof frameStyleText;
|
|
98
|
+
/**
|
|
99
|
+
* The two iframe attributes a snapshot drives, mapped from protocol terms to DOM ones.
|
|
100
|
+
*
|
|
101
|
+
* Every binding applies this on top of {@link AndcoButtonController.IFRAME_SANDBOX} and
|
|
102
|
+
* {@link AndcoButtonController.IFRAME_REFERRER_POLICY}: those two are fixed, this pair changes
|
|
103
|
+
* with the connection.
|
|
104
|
+
*/
|
|
105
|
+
static frameProps(snapshot: AndcoButtonSnapshot): {
|
|
106
|
+
src: string;
|
|
107
|
+
hidden: boolean;
|
|
108
|
+
};
|
|
57
109
|
constructor(options: AndcoButtonControllerOptions);
|
|
58
110
|
get snapshot(): AndcoButtonSnapshot;
|
|
59
111
|
/** Observes lifecycle changes. The return value is the unsubscribe. */
|
|
60
112
|
subscribe(listener: Listener): () => void;
|
|
61
113
|
/** Attaches a mounted iframe. The return value disconnects it. */
|
|
62
114
|
connect(iframe: HTMLIFrameElement): () => void;
|
|
115
|
+
/** Detaches the iframe bridge while retaining listeners and the last iframe for reuse. */
|
|
63
116
|
disconnect(): void;
|
|
64
117
|
/**
|
|
65
118
|
* Returns the surface to its ready state so it can be activated again.
|
|
@@ -70,6 +123,7 @@ export declare class AndcoButtonController {
|
|
|
70
123
|
* recover, so this reconnects against the last-connected iframe instead.
|
|
71
124
|
*/
|
|
72
125
|
restart(): void;
|
|
126
|
+
/** Releases the bridge, iframe reference, and all snapshot listeners. */
|
|
73
127
|
destroy(): void;
|
|
74
128
|
}
|
|
75
129
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/browser/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAIf,eAAe,EACf,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAqB,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,yBAAyB,EAA6B,MAAM,aAAa,CAAC;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,QAAQ,CAAC;AAEzD,wDAAwD;AACxD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAErH,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,oFAAoF;IACpF,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oGAAoG;IACpG,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,wFAAwF;IACxF,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/browser/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAIf,eAAe,EACf,cAAc,EACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAqB,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,KAAK,EAAE,yBAAyB,EAA6B,MAAM,aAAa,CAAC;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,QAAQ,CAAC;AAEzD,wDAAwD;AACxD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;AAErH,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,2FAA2F;AAC3F,MAAM,MAAM,4BAA4B,GAAG;IACzC,oFAAoF;IACpF,MAAM,EAAE,WAAW,CAAC;IACpB,iEAAiE;IACjE,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oGAAoG;IACpG,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,wFAAwF;IACxF,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,6DAA6D;IAC7D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3C,oFAAoF;IACpF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,0FAA0F;IAC1F,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,uEAAuE;IACvE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,mBAAmB,KAAK,IAAI,CAAC;AA6BxD,uFAAuF;AACvF,iBAAS,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAIvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,qBAAqB;;IAChC,kCAAkC;IAClC,MAAM,CAAC,QAAQ,CAAC,cAAc,iFAAkB;IAChD,0CAA0C;IAC1C,MAAM,CAAC,QAAQ,CAAC,sBAAsB,iBAA0B;IAChE,qCAAqC;IACrC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,mCAAqB;IACtD,gCAAgC;IAChC,MAAM,CAAC,QAAQ,CAAC,YAAY,mCAAgB;IAC5C,wCAAwC;IACxC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,mCAAwB;IAE5D;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,cAAc,wBAAkB;IAEhD;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,mBAAmB,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE;gBAkB/D,OAAO,EAAE,4BAA4B;IAKxD,IAAW,QAAQ,IAAI,mBAAmB,CAEzC;IAED,uEAAuE;IAChE,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;IAQhD,kEAAkE;IAC3D,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAiDrD,0FAA0F;IACnF,UAAU,IAAI,IAAI;IAKzB;;;;;;;OAOG;IACI,OAAO,IAAI,IAAI;IAetB,yEAAyE;IAClE,OAAO,IAAI,IAAI;CAmMvB"}
|