@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.
Files changed (83) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +96 -54
  3. package/dist/auth.d.ts +8 -3
  4. package/dist/auth.d.ts.map +1 -1
  5. package/dist/auth.js +32 -23
  6. package/dist/browser/controller.d.ts +55 -1
  7. package/dist/browser/controller.d.ts.map +1 -1
  8. package/dist/browser/controller.js +166 -9
  9. package/dist/browser/frame.d.ts +4 -3
  10. package/dist/browser/frame.d.ts.map +1 -1
  11. package/dist/browser/frame.js +12 -20
  12. package/dist/browser/index.d.ts +18 -6
  13. package/dist/browser/index.d.ts.map +1 -1
  14. package/dist/browser/index.js +64 -29
  15. package/dist/browser/popup.d.ts +6 -1
  16. package/dist/browser/popup.d.ts.map +1 -1
  17. package/dist/browser/popup.js +37 -8
  18. package/dist/cli/index.d.ts +4 -2
  19. package/dist/cli/index.d.ts.map +1 -1
  20. package/dist/cli/index.js +10 -3
  21. package/dist/cli/server.d.ts +5 -0
  22. package/dist/cli/server.d.ts.map +1 -1
  23. package/dist/cli/server.js +5 -0
  24. package/dist/client.d.ts +66 -19
  25. package/dist/client.d.ts.map +1 -1
  26. package/dist/client.js +125 -74
  27. package/dist/config.d.ts +2 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +1 -0
  30. package/dist/credentials.d.ts +58 -4
  31. package/dist/credentials.d.ts.map +1 -1
  32. package/dist/credentials.js +0 -0
  33. package/dist/errors.d.ts +23 -21
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/errors.js +18 -20
  36. package/dist/globals.d.ts +25 -0
  37. package/dist/globals.d.ts.map +1 -0
  38. package/dist/globals.js +15 -0
  39. package/dist/grants-api.d.ts +34 -0
  40. package/dist/grants-api.d.ts.map +1 -0
  41. package/dist/grants-api.js +48 -0
  42. package/dist/grants.d.ts +16 -0
  43. package/dist/grants.d.ts.map +1 -0
  44. package/dist/grants.js +13 -0
  45. package/dist/index.d.ts +12 -6
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +8 -2
  48. package/dist/inflight.d.ts +31 -0
  49. package/dist/inflight.d.ts.map +1 -0
  50. package/dist/inflight.js +26 -0
  51. package/dist/intents.d.ts +386 -40
  52. package/dist/intents.d.ts.map +1 -1
  53. package/dist/intents.js +712 -53
  54. package/dist/oauth.d.ts +55 -6
  55. package/dist/oauth.d.ts.map +1 -1
  56. package/dist/oauth.js +86 -57
  57. package/dist/presenter.d.ts +59 -11
  58. package/dist/presenter.d.ts.map +1 -1
  59. package/dist/presenter.js +40 -1
  60. package/dist/resource.d.ts +109 -0
  61. package/dist/resource.d.ts.map +1 -0
  62. package/dist/resource.js +151 -0
  63. package/dist/rest.d.ts +23 -4
  64. package/dist/rest.d.ts.map +1 -1
  65. package/dist/rest.js +46 -5
  66. package/dist/server/index.d.ts +3 -0
  67. package/dist/server/index.d.ts.map +1 -1
  68. package/dist/server/index.js +1 -0
  69. package/dist/server-metadata.generated.d.ts.map +1 -1
  70. package/dist/server-metadata.generated.js +8 -4
  71. package/dist/service.d.ts +109 -0
  72. package/dist/service.d.ts.map +1 -0
  73. package/dist/service.js +241 -0
  74. package/dist/session-store.d.ts +12 -4
  75. package/dist/session-store.d.ts.map +1 -1
  76. package/dist/session-store.js +64 -14
  77. package/dist/storage.d.ts +16 -23
  78. package/dist/storage.d.ts.map +1 -1
  79. package/dist/storage.js +27 -25
  80. package/dist/tokens.d.ts +46 -0
  81. package/dist/tokens.d.ts.map +1 -0
  82. package/dist/tokens.js +148 -0
  83. 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 Andco
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 is no `AndcoSessionClient` — that type was removed;
65
- `AndcoClient` (unauthenticated arm) and `AndcoClientAuthed` (authenticated arm) are the only two
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
- ## Binding credentials
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 bound = andco.with(await accessTokenFromCookie(request.headers));
88
- const { data } = await bound.rest.http.GET("/accounts");
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
- Any object with that one method works — a Better Auth account, a Passport session, a row in your
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
- `AndcoBoundClient` was removed. In its place, a Resource Server Definition is any object that
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: it only knows how to create, read, execute, and observe an
131
- Intent generically. It does not know about deposits, withdrawals, or any other domain concept —
132
- those belong to whatever Resource Server Definition owns that domain, composed over this same
133
- surface (see [Resource Server Definitions](#resource-server-definitions) above), such as the AndCo
134
- Bank Resource Server Definition in [`@andco/bank-sdk`](../bank-sdk/README.md).
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
- const { data: intent, error } = await andco.intents.create(
138
- "deposit",
139
- depositInput,
140
- {
141
- idempotencyKey: "deposit:8472",
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
- - `create(type, input, options?)` — creates an Intent of the given type; `type` is sent as the
148
- body's own `type`, so the input never repeats it, and the response's `type` is checked against it.
149
- Resolves to `Result<AndcoIntent>` — the Intent union of Andco's own contract — unless a
150
- third-party Resource Server Definition names its own response type.
151
- - `get(intentId)` — reads the authoritative state of one Intent, as `Result<AndcoIntent>` by the
152
- same rule.
153
- - `execute(intentId)` — executes an Intent whose Grant permits it.
154
- - `events(intentId, after?, signal?)` — reads one page of durable Intent facts.
155
- - `subscribe(subject, read, decode, handler, options)` — a durable, paged subscription loop, with
156
- its own checkpoint and poll-interval reconciliation. Returns an unsubscribe function.
157
-
158
- For the actual bank operations — `createDeposit`, `createWithdrawal`, `createAutomaticCharge`,
159
- `closeDeposit`, and account/intent event listeners — see
160
- [`@andco/bank-sdk`](../bank-sdk/README.md), whose `Bank` definition composes this same
161
- domain-neutral surface:
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
- import { Bank } from "@andco/bank-sdk";
188
+ await service.intents({ subject: { type: "organization", identifier: "123" } }).approve(transferId);
189
+ ```
165
190
 
166
- const bankDefinition = new Bank({ clientId, resource: "https://api.andco.cl" });
167
- const bank = bankDefinition.use(andco.with(session));
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
- const { data: deposit, error } = await bank.intents.createDeposit(
170
- depositInput,
171
- {
172
- idempotencyKey: "deposit:8472",
173
- },
174
- );
175
- if (error) throw error;
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
- Present the resulting intent with `@andco/sdk-react`, `@andco/sdk-vue`, `@andco/sdk-svelte`, or
179
- `@andco/sdk-script`. The browser must retrieve the authoritative intent state after presentation;
180
- callback parameters are correlation data, not financial state.
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`, `AndcoCliPresenter`.
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 scopes needed by the current operation.
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 { type AndCoRandomSource } from "@andco/protocol";
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
- /** Random source for the Presentation ID, threaded from the client's `globals.crypto`. */
17
- crypto?: AndCoRandomSource;
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
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAqB,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,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,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,GAAG;IAC3D,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC;IACxB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnF,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAS;;gBAGD,OAAO,EAAE,gBAAgB;IAI5C;;;;;OAKG;IACU,MAAM,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IA4B3F,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;CAWtD"}
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.#options.crypto);
34
- const prepared = await oauth.createAuthorizationRequest(options);
35
- if (prepared.error)
36
- return Result.fail(prepared.error);
37
- await this.#options.persistTransaction?.(prepared.data);
38
- const returnTo = new URL(prepared.data.redirectTo);
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
- url: prepared.data.authorizationUrl,
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
- const exchanged = await oauth.exchangeCallback({ callbackUrl: presented.data, request: prepared.data });
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
- const store = this.#options.store;
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
- onComplete?: () => void;
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,MAAM,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,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;AAExD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAqB;;gBAcb,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;IAiD9C,UAAU,IAAI,IAAI;IAKzB;;;;;;;OAOG;IACI,OAAO,IAAI,IAAI;IAgBf,OAAO,IAAI,IAAI;CA8FvB"}
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"}